Mac OS XにThreadScopeをインストールする(2015年1月版)

HomebrewのGTK+がQuartzバックエンドに対応したので、XのいらないThreadScopeが使えるようになった。手元の * GHC 7.8.4 * OS X Yosemite な環境では次のコマンドでインストールできた。

brew install gtk+ --without-x11
brew install gcc-4.9
cabal update
cabal get threadscope
cd threadscope-<version>
cabal sandbox init
cabal install gtk2hs-buildtools
cabal install --bindir=~/.cabal/bin --datadir=~/.cabal/share --with-gcc=gcc-4.9 -fhave-quartz-gtk

新しいlifted-asyncの使い方

monad-controlが新しくなって、モナド変換子スタックの各層がもつ状態を表すStTStMがassociated data typesからassociated type synonymsに変わった。この変更でmonad-controlに依存するたくさんのパッケージが壊れ、拙作lifted-asyncも漏れなく壊れた。修正がてらいろいろ改善を施したのでその話をしようと思う。

Control.Concurrent.Async.Lifted.Safeモジュール

v0.3.0から新たにControl.Concurrent.Async.Lifted.Safeモジュールが追加された。lifted-asyncをより安全に利用するため、既存・新規のコードにかかわらず使えるところではこちらのSafeモジュールを使い、適用できないところでのみ従来のControl.Concurrent.Async.Liftedを使うべきである。

lifted-asyncとモナドスタックのもつ副作用

そもそも以前のlifted-asyncには重大なデザイン上の欠陥があった。たとえば次のコード片は何を出力すべきだろうか。

import Control.Monad.State
import Control.Concurrent.Async.Lifted

main :: IO ()
main = do
  (r, s) <- flip runStateT 42 $ do
    a <- async $ modify (+1) >> get
    modify (+2)
    wait a
  print (r, s)

async中のmodify (+1)で状態が43になりgetで返ってくる値は43、その後さらにmodify (+2)で状態は45になり(43, 45)か?スレッド起動より先にmodify (+2)が実行され状態が44に、その後modify (+1)が実行され(45, 45)か?いやそもそも同期なしに共有状態を書き換えるのはおかしい。ではどうするべきだったのか?

現在のlifted-asyncの実装では常に(43, 43)が出力される。何故か?実装上の都合でwaitasyncの中で起こったStateT上の副作用をすべて親スレッド側で復帰しているからである。つまりmodify (+2)は暗黙のうちに捨てられる。

どうしてこんな残念なことになっているかを理解するには実装を見てみる必要がある。asyncwaitの実装は次の通り。

async :: MonadBaseControl IO m => m a -> m (Async (StM m a))
async m = liftBaseWith $ \runInIO -> A.async (runInIO m)

wait :: MonadBaseControl IO m => Async (StM m a) -> m a
wait = liftBase . A.wait >=> restoreM

ここでA.がついているものはasyncパッケージの関数を使っている。asyncモナドm上の状態をStM m aという型でAsyncの中に埋め込む。waitは結果を取り出す過程でこの状態を現在のmに復帰する。もし仮にasyncm (Async (StM m a))でなくm (Async a)を返す、またはwaitrestoreMを使わずにaを取り出すことができれば、子スレッド中のm上の副作用はすべて無視するという戦略がとれるが、これをmについて多相的に実装する方法はないと思う。

Safeモジュール

問題の原点に立ち返ってみると、 並行処理を目的としたライブラリが状態を持つモナド上で無制限に利用可能なのがおかしい。状態を持たないモナド、すなわちStM m a ~ aを満たすモナドだけで利用できるモジュールを作ろうというのがSafeモジュールのアイデアである。

Safeモジュールではasyncwaitの型は次のようになっている。

async :: (MonadBaseControl IO m, StM m a ~ a) => m a -> m (Async a)
wait :: (MonadBaseControl IO m, StM m a ~ a) => Async a -> m a

制約部にStM m a ~ aが追加され、この制約からAsync (StM m a) ~ Async aがいえるため、型シグネチャがasyncパッケージに近い直感的でわかりやすいものになった。実装は一部を除いてControl.Concurrent.Async.Liftedの関数をそのまま使っていて、単に型シグネチャだけ書き直している。

Safeモジュールでは先に示した副作用の問題は起こらない。なぜなら副作用が問題になるStateTなどはStM m a ~ aを満たさないため、そもそもこのモジュールを利用できないからである。現実のコードでは並行処理が必要かつ共有状態を持つモナド変換子スタックはReaderTMVarなどを持たせているはずなのでこの制約は問題にならないことがほとんどではないかと思う。

Concurrentlyの実装上の問題

当初Safeモジュールを実装したときにConcurrentlyApplicativeインスタンスを書くのは困難に見えた。理由はStM m a ~ aという制約はmが状態を持たないという制約であるだけでなくaStM m aと等しくなるというようにaに対して言及しているために、abに何の制約も持たない(<*>) :: Applicative f => f (a -> b) -> f a -> f bと型が合わないように見えたためである。

この問題は@feuerbachさんがconstraintsパッケージを用いたすばらしい解決策を示してくれたConcurrentlyの実装にはForall (Pure m)という制約が出てくる。これは次の擬似コード相当のことをしていると考えられる。

data Concurrently m a where
  Concurrently
    :: (forall a. (StM m a ~ a))
    => { runConcurrently :: m a } -> Concurrently m a

instance (MonadBaseControl IO m, forall a. (StM m a ~ a)) => Applicative (Concurrently m) where
  ...

つまりここで必要なStM m a ~ aという制約はmに対するもので、aはなんであってもよいはずである。しかし現在のGHCはuniversally quantifiedな制約はサポートしていないのでconstraintsパッケージを使っている。

universally quantified constraintsの使い道やconstraintsパッケージの実装の話はSafe, polykinded universally quantified constraintsというポストが詳しい。

Concurrently b m ab

ほかにもマイナーな変更を入れた。

当初lifted-asyncにConcurrenlyを移植した際にUndecidableInstancesを嫌ってbという何もしない型変数を導入したが、これがConcurrentlyの見た目を怖くしていたようで、どうやって使うのかと質問している例をいくつか見たことがあった。 改めて見直してみるとMonadBaseControlインスタンスで型検査をループさせることは難しそうであること、仮にそのようなインスタンスを書いてしまっても問題箇所の発見は難しくなさそうであることから、UndecidableInstancesを有効にして型変数bをなくした。先のconstraintsパッケージの影響でまた別の意味で怖い型になってしまったが、型の読みやすさという点である一定の効果はあったのではないかと思う。

今後

今のところlifted-asyncに大きな変更を加える予定はなく、地道にGHCや依存パッケージの更新に合わせてマイナーアップデートをしていくことになると思う。Haddockコメントを改善したいとは考えているけど、あまりやる気が出ないのでPR歓迎。ともあれStackageにも入っているし少しずつ利用者も出てきたようなので、モナド変換子スタックでasyncを使いたい人は是非使ってみてほしい。

Cabal sandboxで実行ファイルをインストールする

c2hsとかthreadscopeなど、ライブラリではなく実行ファイル + αを含むパッケージをcabal hellに陥ることなくホームディレクトリの.cabalにインストールするには--bindirと--datadirを指定してsandboxにインストールすればよい。

cabal get threadscope
cd threadscope-0.2.6
cabal sandbox init
cabal install --bindir ~/.cabal/bin --datadir ~/.cabal/share

最後のコマンドは呪文みたいなものなのでシェルの履歴に入れておこう。

こうすればsandboxにインストールしているのでユーザのパッケージDBを壊すこともないし、実行ファイルとそれに必要なデータファイルは~/.cabalにインストールされるので、ソースディレクトリを削除しても問題無い。

Debug.Traceで関数の引数を表示するイディオム

Haskellでよくある複数行の関数定義だったり、ガードを使っている場合、全ての場合について引数をtraceするコードが書きにくいことがある。例えば、

data A = A1 | ...
data B = B1 | ...
data C = C1 | ...

f :: A -> B -> C -> ...
f A1 b c = ...
f a B1 c = ...
f a b C1 = ...

こんな定義でaとbとcをどの場合についてもtraceしたい場合、各行にそれぞれtraceを書く必要がありそうに見える。

f :: A -> B -> C -> ...
f A1 b c = traceShow (A1, b, c) $ ...
f a B1 c = traceShow (a, B1, c) $ ...
f a b C1 = traceShow (a, b, C1) $ ...

そういう場合はこうすると良いらしい。

f :: A -> B -> C -> ...
f a b c | traceShow (a, b, c) False = undefined
f A1 b c = ...
f a B1 c = ...
f a b C1 = ...

Debugging - HaskellWiki

HackageでHaddockが生成されない時は

ここのところずっとHackageでHaddockの生成ボットが止まっているようで、新しくアップロードされたパッケージはたいていドキュメントが無い。どうも新しいサーバにHackageサーバを移しているようなのだけど、きっと忙しいんだろう。

自分で管理しているパッケージに関してはローカルでcabal haddockしたものをアップロードすることができるので、ここら辺を参考にすると幸せになれる。

上のページのhackagedocsはパスワードに特定の記号が含まれているとこけるので、URLエンコードする版を貼っておく。

あるパッケージのバージョンによって別のパッケージへの依存関係を変更する

具体的にはaesonのバージョンが新しい場合にのみscientificを依存関係に組み込みたいとする。

library
  build-depends:
    (aeson >= 0.3.2.5 && < 0.7.0) || (aeson >= 0.7.0 && scientific)

こんな感じにできないかなぁと思っていたが、これはparse errorになり動かない。同僚に聞いてみたところ、flagを使うのが定石らしい。

flag aeson070
  default: False
  manual: False

library
  if flag(aeson070)
    build-depends:
        aeson >= 0.7.0
      , scientific
  else
    build-depends:
     aeson >= 0.3.2.5 && < 0.7.0

aeson070という名前は何でも良い。なぜならmanual: Falseの場合*1、Cabalはaeson070 == Falseでインストールを試み、失敗した場合は自動的にaeson070 == Trueにして再度インストールしようとする。要するにユーザが手で指定するフラグでは無いので何でも良い。

*1:Falseがデフォルト値

hpc-coverallsでHaskellプロジェクトのテストカバレッジを可視化する

GHCには昔からHaskell Program Coverage (HPC)というカバレッジを計測する機能とツールがついてくるのだけど、コマンドラインオプションの指定が面倒だったり、そもそもHPCにバグがあって使いづらいためか、あまり広くは使われてこなかった。

ところが最近になって状況を一変させるかもしれない、hpc-coverallsというパッケージがHackageに上がっていることに気がついたので使ってみた。

Coveralls自体は広く使われてるので解説はいらないと思う。GitHubCoverage Statusみたいなバッジをよく見かけるはず。

hpc-coverallsの使い方は簡単で、Hackageのドキュメントにあるとおり

  • before_installでcabal install hpc-coveralls
  • cabal configureする時に、--enable-library-coverageをつける
  • cabal testの代わりにrun-cabal-testする
  • after_scriptでhpc-coveralls <テスト名>

とすれば良い。ただし、

  • hvr/multi-ghc-travisと併用する場合
    • cabalコマンドがcabal-1.18という名前なので、run-cabal-testする前に、パスが通っているところにln -s /usr/bin/cabal-1.18 /path/to/cabalする必要がある
  • パッケージの依存関係を満たせずにコケる場合
    • cabal install --only-dependenciesした直後に--avoid-reinstallsをつけてhpc-coverallsすると、自分のパッケージの依存関係を壊すことなくインストールできるかもしれない

あたりに気を付けると良い。influxdbとlifted-asyncで設定してみたので、設定例が見たい方はこちらをどうぞ。

実際に使い始めると、hpcコマンドを使うよりはるかに簡単で見やすいので、カバレッジを取りたいならおすすめ。hpc-markupのような1行の中でどこが実行されたかのような細かい情報までは取れないけど、よっぽど1行が長いのでなければ十分なんじゃないかと思う。

f:id:maoe:20140409171323p:plain
f:id:maoe:20140409171331p:plain
f:id:maoe:20140409171337p:plain