読者です 読者をやめる 読者になる 読者になる

北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

stackのコードを読む(2)

stack.yaml はどこで読むのか

Runner.hs#withBuildConfigAndLock なんてのがあって、ここで読んでいる。実際に読んでいるのは Config.hs#loadProjectConfigとかで、こいつが FromJSONのインスタンス になっていてパースしてる。設定の優先順位とかは この辺 で決まってる。設定は Monoid として定義されていて、ほとんどはFirstだが物によっては挙動が違うので注意

Resolver のロード

build を呼ぶお膳立てをしてくれる withBuildConfigAndLock から潜っていくと BuildConfigを読むための関数を作っている。ここで利用する loadBuildConfigBuildPlan#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 > BuildConfigNoLocalBuildConfig
    • BuildConfigNoLocalEnvConfigNoLocalEnvConfig
    • ConfigLoadConfig
    • 例えばここに書いたデータ型はすべて 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:バックスラッシュなんだけどね・・・

式と型と値

JavaHaskellも静的型付けの言語だけど、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:依存型が入ってくると互いに密接に関係するようになるけど

Surface Studio の電源が落ちた

昨日の午前中までは安定していたSurface Studioが、昨日の午後から突然画面が暗転するようになってしまった。初めスリープから復帰できずにシャットダウンしたので、 Surface Book がスリープからの復帰がかなり不安定だったこともあってそれと同じかなくらいに思っていた。

ところが、その後電源を上げても、5分程度でまた暗転してしまう。いくら立ち上げても暗転するので、ほんとビビった。結果としてコンセントをタップから根本に差し直したところ安定したのだけど、これが原因かはわからない。暗転時に、本体から今まで聞いたことがないWindows10のサウンドが聞こえていたので、それが関係あるのかもしれない。今度発生したら録音する。

ghcと文字コードとWindowsと

ghc さんは、エラーメッセージに ascii の範囲外の文字を平気で使ってくる。これ、環境によってはハマるので勘弁して欲しい。具体的にはWindowsなんだけど。

例えばこんなメッセージ。

Prelude> 3 / 4 :: Int

<interactive>:1:1: error:
    • No instance for (Fractional Int) arising from a use of ‘/’
    • In the expression: 3 / 4 :: Int
      In an equation for ‘it’: it = 3 / 4 :: Int

メッセージ作ってる ここ とか ここ を見る限り、環境によっては使わないっぽい。この使うかの判定は ここ でやってる。ロケールエンコーディング"‘’" が可逆に変換できるか見てるっぽい。可逆であればいいので、ロケールがユーザが望むエンコーディングに設定されているかは関係ない。つまり、文字化けの起こる状況でも容赦なく使ってくる。

で、 localeEncoding がどう決まるかだが、 Windowsか否かで判定が別れているWindowsだと code pageを使って判定している ようで、 chcp 65001 している環境だと UTF-8 になる。 932 の状態だと CP932 で入出力を扱おうとするので、 UTF-8 を期待するようなツールがどっかに入っていると面倒なことになる。というか、今時 CP932 でも動くことを想定したツールなんてほとんど無い気がする。

入門Haskell(10年前の本)

久々に入門Haskellのsamegameを作る章を読んだ。本を買った当時は頭に入ってこなくて流し読みしかしなかった記憶があるけど、今読むと書いてあることはシンプルだった。

この本に問題があるわけではなく10年前の本なので当たり前だけど、なんだかなあと思うところが多々ある。

  • なんでもかんでも mapfilterzipfoldr で済ませようとする
  • なんでもかんでも 1 行で書こうとする
  • where が大好き *1
  • トップレベルの識別子にも型定義してない
  • 2016年ではWin上で HGL はビルドできなかった・・・

この関数とか。

filterBoard :: (Pt -> a -> Bool) -> [[a]] -> [[a]]
filterBoard f board = filter ((/=0).length) $ zipWith (\x column -> concat $ zipWith (\y v -> if f (x, y) v then [v] else []) [0..] column) [0..] board

zipfilter で押し通すにしても、無理せずこれくらいに分けて書いていいんじゃないって思うわけ。

import Data.List (zip3)

filterBoard f board =
  let numbered = zipWith (\x column -> zip3 (repeat x) [0..] column) [0..] board
      filtered = map (map trd . filter f') numbered
  in  filter ((/= 0) . length) filtered
  where
    f' (x, y, column) = f (x, y) column
    trd (_, _, x) = x

*1:let .. in .. 派なものですみません・・・