Haskell学習記(1)

少し前にHaskellの勉強を始めて軌道に乗りだしたので、初志を忘れないように、ポストLYHGGの歩みを書き留めておく。

きっかけ

  • 当時のプログラミング歴:C++ (< C++11) 5年ぐらい, Python 2年ぐらい。
  • Haskellを始めた動機:pythonクロージャーや高階関数、遅延評価をよく使うようになるが、デバッグのコストがかさむ。「静的型付き関数型言語」を使えばこの悩みから開放されるらしいことを知る。
  • 触ってすぐの感想:中置演算子が多い。意味の切れ目がわかりにくい。

倦怠期:~半年

  • とりあえずLearn you Haskell for great good! (LYHGG)をよむ。やはりモナドあたりで消化不良を起こしながら読了。モナドにもいろいろ種類があることを知って動揺する。
  • 試行錯誤。関数オーバーライドと継承がとてもやりにくい事に気が付き失望。C++11/14のラムダ式・関数オブジェクト・autoに浮気するが、Haskellに比べて型が弱いと感じる。
当時Haskellに抱いていたおぼろげなイメージ
  • 全てが不変値だから、数学的な思考と馴染みやすい
  • 求めたい値を宣言的に表現すると、コンパイラが効率の良いアルゴリズムを吐いてくれる(※違う。値を求める方法を宣言的に書くと、時に効率よく実行してくれる)。
  • 宣言的であるがゆえに、手続き的なパラダイムと親しい「アルゴリズム」や「データ構造」と親和しなさそう(※書きやすさという意味では部分的に正しい。ただ、Haskellでは手続き的なアルゴリズムまで含めて宣言的に書くことができる)。

Haskell再び:~1年

仕事用のコードをHaskellで書く、という何度目かのチャレンジ中に、ずっと抱いていた違和感の根っこに気がつく。 「Typeclassは型のインターフェースというより関数のインターフェース」 であるということ。 ある型に作用する関数の集まりから共通の構造を括りだしたものがtypeclassであり、typeclassの主役はそれら関数(e.g. Numクラスの (+), (-), (*), ..)だった。実際、言語拡張無しには同名の関数を異なるtypeclassに所属させることはできない(!)が、同一の型を異なるtypeclassのインスタンスとする事は可能(当然)だ。 これが個人的に思うOOPHaskellの一番の差で、気づいてからモヤモヤがだいぶ晴れた。 この発見を期にHaskellの学習が勢いづくことに。 その試みの中で特に効果のあったと思う事柄は:

  • atomエディタ + ide-haskell導入
  • WIWINWLHを読む。Typed holeの活用。
  • (特に言語拡張について)ghcのドキュメントを読む。NewTypeDeriving拡張は継承に便利。
  • 圏論Haskellのつながりを整理し、モナドを理解する。多くの人がよく言うように使っていくうちに、モナドは結局typeclassの一種でしかないことが素直に理解できるようになる。ただそれがとても汎用的で強力なtypeclassなので、理解に時間がかかった。普通に使っていればモナドインスタンス宣言を書くことはなさそうだというのも個人的に大事な気付きだった。
  • 標準ライブラリのソート関数やordered mapの実装を読む。関数型言語とはいえ吐かれるバイナリは手続き的だ。つまりHaskellは「関数を用いて手続きを記述する言語」だ。この縛り(?)と引き換えに、強力な抽象化の恩恵を受けられているようだ。

これから

  • 頻出ライブラリ(parsec, hmatrix, lens)に親しむ。