kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

第14回 関数脳のつくり方 Second Season 〜モナドで悟りをひらく〜 - 刺激を求める技術者に捧げるScala講座:ITpro へのツッコミ

ITProのScala連載「刺激を求める技術者に捧げるScala講座」の第14回 関数脳のつくり方 Second Season 〜モナドで悟りをひらく〜はてなブックマークで150ブクマ超えるなど注目を集めていますが、読んで見ると初学者に誤解を与える箇所が散見されるので、一応、連載に関わってる者(といっても1回記事書いただけですが)の一人としてツッコミを入れさせていただきます。

  • 「参照透明性を保持しながら手続き型的な記述をするための枠組み」?(p.1)

モナドとは,関数型言語で,参照透明性を保持しながら手続き型的な記述をするための枠組みです。

早速ですが、これは、IOモナドやStateモナドには当てはまっても、全てのモナドについて当てはまるものではないですよね。たとえば、Maybeモナドではこれは当てはまりません。

これはページタイトルの一部ですが、本文を読んで見ると、どちらかというとIOモナドが必要な理由に関する話であって、これをモナド一般の話として語るのはちょっとまずいのではないかと思います。

  • 「遅延評価をするためには,「参照透明性」が必要」?(p.3)

遅延評価をするためには,「参照透明性」が必要です。すなわち,副作用のないプログラムである必要があります。副作用のイメージは「第7回 関数脳のつくり方 First Season」で紹介していますので,イメージがわきにくい人は読んでみてください。

以降の文章で説明されている通り、一般に遅延評価と副作用を混ぜ合わせるのが危険なのは確かですが、それをもって、「遅延評価をするためには,「参照透明性」が必要」というのは強過ぎる主張かと思います。遅延評価と副作用は相性が悪いという程度が穏当な表現ではないでしょうか。

  • 関数型言語で「副作用」を持つプログラムを書くことは基本的にはできません。」?(p.3)

したがって,関数型言語で「副作用」を持つプログラムを書くことは基本的にはできません。

関数型言語で「副作用」を持つプログラムを書くことは基本的にできません」というのはマズイです。関数型言語の定義にもよりますが、一般的に関数型言語だとされている、ML系の言語では、参照型というものがあり、普通に副作用を扱うことができます(裏技的な機能としてではなく。追記:あと、I/Oのような副作用ももちろん普通に扱えます。ここでの普通というのは、JavaとかCとかの手続き型言語と同程度に簡単に扱えるという程度の意味)。純粋関数型言語に限定するなら正しいですが、関数型言語=純粋関数型言語という認識は一般的でないと思います。

  • filterは必要?

Scalaのモナドはクラスで表現されます。クラスはmap,flatMap,filterの3つ,さらに,「ユニット」コンストラクタと呼ばれる,要素数からモナドクラスをインスタンス化するコンストラクタを持っています。

Scalaのfor式で扱うためには、filterがあった方が何かと便利ではあるのですが、モナドにとっては全く本質的でないので、省いた方が良い気がします。

さて,もう一つの例はListモナドです。これも先の「ふつうのHaskellプログラミング」で出てきたサンプルをScalaに直したものです。このモナドは「関数を適用するたびに,値が増えたり減ったりする関数(演算)をつなぎ合わせる」ときに使います。

「関数を適用するたびに,値が増えたり減ったりする関数(演算)」だと意味がよくわからないです。念のため、参考にされた、All About Monadsでは

List モナドは非決定性をもつ計算の連鎖を、演算をそれぞれのステップで可能なすべての値に適用することで、合成する戦略を内包しています。これは計算が曖昧性を扱わなければならないときに有用です。そのような場合、すべての可能性を、曖昧性が解決するまで、探索することを可能にします。

のように書かれています。これはこれでなかなかわかりにくい説明ですが、要は、答えが複数通りあるような計算(たとえば、演算子の優先順位が不明な場合、1-2+3という数式は、(1-2)+3と1-(2+3)の二通りにパースできます)をつなぎ合わせるのがListモナドだということです。

あと、p.8では、「表1●モナドの種類(All About Monadsより)」として、いくつかのモナドについて名称と概要が述べられていますが、概要のところ(特に、List,IO)は、All About Monadsのところからの引用ではなく、牛尾さんの解釈が入っているので、そこは読者が勘違いしないように、あらかじめ述べておくべきではないでしょうか。

  • すべてのfor構文は,map,flatMap,filterの3つの高階関数によって置き換えることができる?

すべてのfor構文は,map,flatMap,filterの3つの高階関数によって置き換えることができます(これはモロにモナドの条件で出てきたメソッドですね)。

細かいですが、yieldを使わない場合、foreachが出てくる可能性もありますね。


追記:
ツッコミを淡々と列挙しただけなので誤解を与えてしまったかもしれませんが、あくまで自分の目から見てここは直した方がいいんじゃないかなあという点を列挙しただけあって、別に牛尾さんの記事には価値が無いから読むべきではないとかそういう意図では無いです。もし、気分を害してしまったなら申し訳ないです。初学者に対して説明をするに当たって、ときに「説明のためのウソ」が必要な事も理解しています。ただ、「説明のためのウソ」としても不必要であり誤解を招く(ように自分の目から見える)部分がいくつかあって、それが気になってしまったというのはあります。たとえば、

したがって,関数型言語では「副作用」を持つプログラムを書くことは基本的にはできません。

は、単に「関数型言語では「副作用」をできるだけ使わないプログラムを書くことが推奨されます」くらいで問題無いですよね。元の文だと、関数型言語=純粋関数型言語みたいな先入観が形成されそうで、自分としてはどうもムズムズしてしまいます。余計なおせっかいと言われればそれまでなのですが、ご容赦いただければと思います。