Smartphonemaoeのブログこの広告は、90日以上更新していないブログに表示しています。mizchi.hatenablog.comHaskellを習得する上で難しいポイントだと思います。大きく分けると次の二つにまとめられるのではないかと思います。コードの中で現れる識別子からそれが何なのかを探しづらいCabalがつらいそれぞれ個人的な見解を書いてみます。え〜どっちがApplicativeで何がFunctorだっけ、そもそもその定義はなんだったっけ。え〜あ〜〜〜みたいになる。名前空間のそして名前で役割を推測することが困難な事が多々ある。あって、っていう段階があるのは理解しているけど、ソース読んでて突然出現するそれには全く対応できない。どちらも識別子から型がわかれば大部分が解決します。ありがたいことに近年はghc-modをベースにしたサポートを各エディタから利用できます。Haskell、Atomでもide-haskellを使えば、カーソル下の識別子の型を簡単に確認できるようになります。あとはhasktagsなどでタグジャンプできるようにしておくのもよいと思います。これに加えてHoogleやHayoo!は演算子の検索も充実しているので役に立ちます。(bar)みたいに絞るのか、それすらわからない(後者のような気はしているが)。これに関しては結構意見が分かれるかもしれません。importすべしという過激意見もありますが、タイプ量がだいぶ増える上にqualifyされた演算子はお世辞にも読みやすいとはいえません。すべての識別子を明示してimportするのもimportリストの管理が煩雑になりますし、80桁に押さえようとするとimportリストが複数行にまたがるようになって、ただでさえ長くなりがちなリストがどんどん長くなります。etc...みたいな感じで運用しています。ちなみに、少し話題がずれますがコードベースが巨大になってくるとimportリストよりexportリストをちゃんと管理することが重要で、モジュール内の何を外に見せて何を隠すかをよく考えることが肝心です。Cabalつらい問題sandboxが導入されてからはある程度過去の問題になりつつあると思います。cabalにはsandboxを強制onにするオプションがあるので必要なら使うとよいと思います。Trueと書くことで有効にできます。あと、cabalファイルの書式はtoml形式ではないのでtomlのことは忘れましょう。hellが起こることほとんどないと思います。Platformを入れていてHPで提供されているパッケージの新しい版をインストールしたり、GHCのブートパッケージのバージョンを変えようとするとつらいことはあるかもしれません。Cabalのつらさは皆痛いほど経験してきているので、Stackageという人力で安定しているパッケージセットを作るという涙ぐましい努力が行われています。これを利用するのも一つの手でしょう。comment2015-02-11debパッケージをデバッグシンボル付きでリビルドするHaskellのFFI呼び出しで返ってくる値がおかしいのでgdbでデバッグしたいという時、*-dbgパッケージがあればいいのだけどない場合は自分でパッケージをリビルドする必要がある。fooこれでfooディレクトリの下にdebファイルができるのでインストールすればいい。あとはgdbなりcgdbなり適当なフロントエンドを使ってデバッグする。comment2015-02-05GHCのヒーププロファイルの結果を適当に間引くhaskellGHCのヒーププロファイラが出力する.hpファイルは、計測対象のプログラムが動いている間ずっと追記されるので、長時間動かしているとファイルがとても大きくなって、hp2any-managerなどで開けなくなってしまう。いう場合は.hpファイルの中身を適当に間引くといいそうなので、ghpというスクリプトを書いた。big.hpとすると10サンプル中から1サンプルに間引いて標準出力に出力する。XにThreadScopeをインストールする(2015年1月版)haskellHomebrewのGTK+がQuartzバックエンドに対応したので、XのいらないThreadScopeが使えるようになった。環境では次のコマンドでインストールできた。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には重大なデザイン上の欠陥があった。次のコード片は何を出力すべきだろうか。45)か?いやそもそも同期なしに共有状態を書き換えるのはおかしい。?実装上の都合でwaitがasyncの中で起こったStateT上の副作用をすべて親スレッド側で復帰しているからである。(+2)は暗黙のうちに捨てられる。してこんな残念なことになっているかを理解するには実装を見てみる必要がある。asyncとwaitの実装は次の通り。restoreMここでA.がついているものはasyncパッケージの関数を使っている。aという型でAsyncの中に埋め込む。waitは結果を取り出す過程でこの状態を現在のmに復帰する。a)を返す、またはwaitでrestoreMを使わずにaを取り出すことができれば、子スレッド中のm上の副作用はすべて無視するという戦略がとれるが、これをmについて多相的に実装する方法はないと思う。並行処理を目的としたライブラリが状態を持つモナド上で無制限に利用可能なのがおかしい。aを満たすモナドだけで利用できるモジュールを作ろうというのがSafeモジュールのアイデアである。Safeモジュールではasyncとwaitの型は次のようになっている。aがいえるため、型シグネチャがasyncパッケージに近い直感的でわかりやすいものになった。実装は一部を除いてControl.Concurrent.Async.Liftedの関数をそのまま使っていて、単に型シグネチャだけ書き直している。Safeモジュールでは先に示した副作用の問題は起こらない。aを満たさないため、そもそもこのモジュールを利用できないからである。現実のコードでは並行処理が必要かつ共有状態を持つモナド変換子スタックはReaderTにMVarなどを持たせているはずなのでこの制約は問題にならないことがほとんどではないかと思う。当初Safeモジュールを実装したときにConcurrentlyのApplicativeインスタンスを書くのは困難に見えた。bと型が合わないように見えたためである。この問題は@feuerbachさんがconstraintsパッケージを用いたすばらしい解決策を示してくれた。m)という制約が出てくる。これは次の擬似コード相当のことをしていると考えられる。aという制約はmに対するもので、aはなんであってもよいはずである。quantifiedな制約はサポートしていないのでconstraintsパッケージを使っている。constraintsというポストが詳しい。aのbほかにもマイナーな変更を入れた。当初lifted-asyncにConcurrenlyを移植した際にUndecidableInstancesを嫌ってbという何もしない型変数を導入したが、これがConcurrentlyの見た目を怖くしていたようで、どうやって使うのかと質問している例をいくつか見たことがあった。見直してみるとMonadBaseControlのインスタンスで型検査をループさせることは難しそうであること、仮にそのようなインスタンスを書いてしまっても問題箇所の発見は難しくなさそうであることから、UndecidableInstancesを有効にして型変数bをなくした。先のconstraintsパッケージの影響でまた別の意味で怖い型になってしまったが、型の読みやすさという点である一定の効果はあったのではないかと思う。今のところlifted-asyncに大きな変更を加える予定はなく、地道にGHCや依存パッケージの更新に合わせてマイナーアップデートをしていくことになると思う。Haddockコメントを改善したいとは考えているけど、あまりやる気が出ないのでPR歓迎。Stackageにも入っているし少しずつ利用者も出てきたようなので、モナド変換子スタックでasyncを使いたい人は是非使ってみてほしい。hellに陥ることなくホームディレクトリの.cabalにインストールするには--bindirと--datadirを指定してsandboxにインストールすればよい。~/.cabal/share最後のコマンドは呪文みたいなものなのでシェルの履歴に入れておこう。すればsandboxにインストールしているのでユーザのパッケージDBを壊すこともないし、実行ファイルとそれに必要なデータファイルは~/.cabalにインストールされるので、ソースディレクトリを削除しても問題無い。comment2014-10-31Debug.Traceで関数の引数を表示するイディオムhaskellHaskellでよくある複数行の関数定義だったり、ガードを使っている場合、全ての場合について引数をtraceするコードが書きにくいことがある。...こんな定義でaとbとcをどの場合についてもtraceしたい場合、各行にそれぞれtraceを書く必要がありそうに見える。...そういう場合はこうすると良いらしい。