最近ひどく疲れているので http://www.toshin.com/center/ から英語を解いた。リスニングが42点で筆記が169点。おもったよりひどくないと思ってしまったけど、本来満点取らないと駄目なやつだよね、これ。現役の頃を思い出して気分転換にはなったので良しとする。こうやって明確に解法も解答も決まっているのは頭の準備体操にはいいね。
stackのコードを読む(3)
- リソルバのダウンロード先URLのデフォルトは Stack.Config.Urls で定義されているがコンフィグで上書きできる
- リゾルバはYAMLでこんな感じで全パッケージとバージョンが書かれてる
packages: drawille: .. 省略 .. version: '0.1.2.0' .. 省略 .. description: cabal-version: '1.10' modules: - System.Drawille provided-exes: - image2term - senoid packages: base: components: - library - test-suite range: ==4.* hspec: components: - test-suite range: ! '>=1.11 && <2.4' containers: components: - library - test-suite range: ==0.5.* QuickCheck: components: - test-suite range: ! '>=2.6' tools: {} shakespeare: .. 省略 ..
- パッケージがビルドできるかは Stack.Build.Source で決めている
- この判定に必要な
SimpleTarget
は Stack.Build.Target#resolveRawTarget で決めてる - コマンドラインから入力したターゲットは RawTarget の形式
ghci 7.10 と 8.0 のレコード記法の違い
DuplicateRecordFields
を実装した影響だと思うけど、 ghci で重複するレコード名を定義したときの振る舞いが 7.10 と 8.0 で違ってる。
まず 7.10 。「フィールド定義が消される」という振る舞いに見える。
$ stack --resolver ghc-7.10 ghci Configuring GHCi with the following packages: GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help Prelude> data X = X { x :: Bool } Prelude> data Y = Y { x :: Int } Prelude> let anX = X { x = True } <interactive>:18:11: Constructor ‘X’ does not have field ‘x’ In the expression: X {x = True} In an equation for ‘anX’: anX = X {x = True} Prelude> let anX = X { x = 10 } <interactive>:19:11: Constructor ‘X’ does not have field ‘x’ In the expression: X {x = 10} In an equation for ‘anX’: anX = X {x = 10} Prelude> :t anX <interactive>:1:1: Not in scope: ‘anX’ Perhaps you meant one of these: ‘and’ (imported from Prelude), ‘any’ (imported from Prelude)
8.0 で同じことをやってみる。フィールドがないことを検知してないように思える。気持ち悪いのは、 True
を渡すと値を生成できてしまうこと。しかも True
はなかったことにされて x
フィールドの値を評価すると例外が投げられる。
$ stack ghci Configuring GHCi with the following packages: GHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for help Loaded GHCi configuration from C:\tools\msys64\tmp\ghci1668\ghci-script Prelude> data X = X { x :: Bool } Prelude> data Y = Y { x :: Int } Prelude> let anX = X { x = True } <interactive>:67:11: warning: [-Wmissing-fields] • Fields of ‘X’ not initialised: x • In the expression: X {x = True} In an equation for ‘anX’: anX = X {x = True} Prelude> let anX = X { x = 10 } <interactive>:68:19: error: • No instance for (Num Bool) arising from the literal ‘10’ • In the ‘x’ field of a record In the expression: X {x = 10} In an equation for ‘anX’: anX = X {x = 10} Prelude> :t anX anX :: X Prelude> let (X x') = anX in x' *** Exception: <interactive>:3:11-24: Missing field in record construction x
これが起こるのは REPL 上だけで、ソースコード上でフィールド名が重複していれば当然コンパイルが通らないので、大きな問題ではないだろう。そもそも 7.10 系の振る舞いが理想的かってのもある。バグな気はするけど。
stackのコードを読む(2)
stack.yaml
はどこで読むのか
Runner.hs#withBuildConfigAndLock なんてのがあって、ここで読んでいる。実際に読んでいるのは Config.hs#loadProjectConfigとかで、こいつが FromJSONのインスタンス になっていてパースしてる。設定の優先順位とかは この辺 で決まってる。設定は Monoid
として定義されていて、ほとんどはFirstだが物によっては挙動が違うので注意。
Resolver
のロード
build
を呼ぶお膳立てをしてくれる withBuildConfigAndLock
から潜っていくと BuildConfigを読むための関数を作っている。ここで利用する loadBuildConfig
が BuildPlan#loadResolver の先で ダウンロードなどをしている。
用意された lcLoadBuildConfig
は buildが呼ばれる前に使われる 。この段階で MiniBuildPlanが決定されて コンパイラのバージョンが決まっている。
stackのコードを読む
stackのコードを読んでるのでメモ。まだ全然肝心なところに入ってない。
- パーサーでコマンドライン引数をパースして、パースの結果として得られる関数を実行する作り
- optparse-applicativeをフォークして使っている
- なんの処理を加えてるのかは読んでない
- 例えばbuildだと、実際に走る処理は
buildCmd
となる - 例えば buildCmd だと、コマンドのオプションをパースした結果
BuildOptsCLI
を元にパース結果としてGlobalOpts -> IO ()
型の処理を生成して返す (run
に束縛される) GlobalOpts
のパース結果は addCommand によって作られ、run
という名前で束縛された実際の処理の引数として使われるbuild
コマンドの本命は Stack.Build.build- 設定は Config.hs にて継承関係で定義されている
- 継承関係と言ってるのは、フィールドを用意して明示的に親のデータを持っているという意味
Config
>BuildConfigNoLocal
>BuildConfig
BuildConfigNoLocal
>EnvConfigNoLocal
>EnvConfig
Config
>LoadConfig
- 例えばここに書いたデータ型はすべて
HasConfig
のインスタンスで、configL
というレンズを通してConfig
のデータに読み書きできる - DefaultSignatures拡張による
default
キーワードを使っている(この拡張、知らなかった)
Data.List.(\\)
(\\) という演算子 *1 でリストの差を取れるのだけど、こいつは最初の出現しか削除してくれない。というのを知らなくて、バグった。
Prelude> import Data.List Prelude Data.List> [1, 2, 2, 3, 3, 1] \\ [1, 2, 3] [2,3,1]
複数引くとその個数だけ消える。
Prelude Data.List> [1, 2, 2, 3, 3, 1] \\ [1, 2, 2, 3] [3,1]
全部消したければ filter
で。
Prelude Data.List> filter (`notElem` nub [1, 2, 2, 3]) [1, 2, 2, 3, 3, 1] []
*1:バックスラッシュなんだけどね・・・
式と型と値
JavaもHaskellも静的型付けの言語だけど、Javaをやっている人とHaskellをやっている人で、「静的型付け」という言葉で頭に描くイメージはだいぶ違うと思う。
じゃあどう違うのかってのを説明すると小難しい話になりがちなのだけど、Haskell 2010 Language Reportにそれを簡潔に示したいい一文を見つけたので記しておく。
An expression evaluates to a value and has a static type. https://www.haskell.org/onlinereport/haskell2010/haskellch1.html#x6-120001.3
Haskellの式に対して、2つの操作が可能である。1つが式を評価して値を求めること。もう1つが、式を型付けして型を求めること。この観点に立つと、型を持っているのは値ではなく式である。そして、評価と型付けが互いに完全に独立した操作であるからこそ、型付けが一種のプログラムのバリデーションとして機能するのである*1。まったく別の操作であるはずの評価と型付けについて、きちんと互いに矛盾の起きない結果が得られるかどうかは、言語設計者の責任に委ねられる。例えば、評価して True
となる式は、型付けすると Bool
型になるように評価と型付けが設計されていなければならない。
こういう話は型システム入門にしっかりと(しっかりすぎるほど)書かれている。
が、それをこの1文で簡潔に表しているのはいいなあと思う。Haskell 2010 Language ReportはHaskellの基本的な部分についてどういう仕様なのか、なぜそうなっているのか、きちんと書かれている原典なので、たまに読むと得られるものが多い。GHCの拡張がないとコードが書けないという人であればなおのこと。
*1:依存型が入ってくると互いに密接に関係するようになるけど