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

uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかFregeとかJavaとか -

モナドはなぜHaskellでしか積極的に使われていないのか?

Haskell monad

(Haskellな日々になってるな…。)

モナドというものがあり、Haskellで有名ですが、実際には、Java8のOptional、ScalaのOptionやfor内包表記などでは使用されています。ScalazというScalaのライブラリや、monadlogieというGroovyのライブラリでも使われています。

とはいえ、一般に、Haskellでのように積極的には使われていないというのが公平な見かたでしょう*1Haskellでは本当にいろんなものがモナド化されています。入出力(IO)、状態、失敗するかもしれない計算(Maybe、Either)、非決定計算、継続、パーサ(モナディックパーサ)、リーダ、ライタ、etc.etc……。

なぜこのような差が生じるのでしょうか?

その前に、まず押さえておくべきことは、モナドは非常に汎用的な機能だということです。数学的定義はともかく、機能的に言うと、一連の処理(計算)を実行するにあたって、ぞれぞれのステップでその入力(引数)と出力(返り値)をキャプチャして、各段階で多様な処理を挟み込み、表面上の計算に対する追加的な別の意味(文脈)を外付けで付与することがモナドの機能です。つまり「ステップを踏む一連計算」なら何でもモナドにできます。

このような「文脈の付与」は、Groovyでは例えば「ビルダー」や「DSL」で見ることができます。これらでは、プログラムコードの表面上書かれていることと、実行していることが別ですよね。

GroovyでビルダーやDSLを実装するにあたり、モナドが不要である理由は、おそらく、文脈に状態を持たせる方法がとれるからです。HogeBuilderが{..}中の、メソッド呼び出しやプロパティ評価の結果を受けてなんらかの構造を構築する場合、ビルダー内の状態を更新することで実装するのが自然です。しかし、Haskellは参照透過なので、1つのメソッドの呼び出しと結果から新規に「モナド値」を作り、それを受け渡す*2ことでしか、文脈に必要な情報を維持管理できないのです。

おそらく他の言語では、モナドに相当することは

  • リフレクション
  • バイトコードエンジニアリング
  • AST変換
  • メタプログラミング
  • AOP
  • 構文規則の自己拡張

などで実現するのが普通だと思うのですが、上記で達成できることを(全てではないにせよ)、参照透過という厳しい制約の元で、一つの型定義と2個かそこらの関数を書くだけで実現できる、ある意味単純なモナドなる1概念がこなしてのけるのは、非常なおどろきです*3

逆に言うと、副作用を持てる言語ではモナドを使う必要がないところを、純粋なHaskellではモナドを使うしかない。それが良いかどうかは別として、純粋さをとことん追求した必然の孤高の帰結がモナドというわけです。

*1:私が知らないだけかもしれませんが。

*2:正確に言うとラムダ式のスコープも一役買ってます

*3:本当に見やすいか?とかは疑問だし、理解しやすいかと言うと端的に言って非常にわかりにくいと思いますが。