東北から電波に乗せて

id:wpw:20051118にて。お返事が返ってきますた。書いてないことまで読み取って要約してくださって毎度のことながらありがたい限りです。結局のところ、どっちでもいいような型の記述の部分ではどっちでもいいのかなあ?といった感じでしょうか。
考えていて思ったのだけれど、どちらも自由なんだけども自由の履き違えは良くないよね〜みたいな話になるのだろうか。たとえて言うならば、ある場所に出席するときの服装が自由だとして型を記述するのを服だとすると、Ocamlの場合には特に服装に指定はしないけど、裸だと恥ずかしいのは当然だから適当に服は着て行ってね、といった感じ。Haskellは、とりあえずスーツを着ていけばいいと思うんだけど、まあ自信があればもうちょっとラフな格好をしていってもいいよ、といった感じ。で、id:wpw氏みたいな人は、Ocamlみたいに指定がない場合は本当に裸で行きかねないので、気をつけたほうがいいという感じだろうか。
うん、なんか分かった気がする(勘違い)

遅延評価

id:Uuutokuda:20051117:1132202387に関して。
このソースコード、まずOcamlの方を見てみる。書いたのはk.inabaさん。D言語を操ったりするらしい。zipperについてよく分からないのでなんとも言いがたいのだけど、多分遅延評価は使っていないのではないかと思う。
で、次にHaskellのコードなのだが、Haskellなのでもちろん遅延評価なのだが、別にこのソースは遅延評価の特性を使ってはいない。副作用もないようなこんなソースコードでは、別に遅延評価をする価値もないし、遅延評価をして結果が変わるわけもない。そもそも、遅延評価かどうかが大きくかかわってくる場合というのは、今書いた「副作用がある場合」というのが一つ。そしてもう一つは再起にかかわるループが止まらないケースがありうる場合の二つだ。つまり、Haskellで書いた場合にはループがとまるのに、Ocamlで書いた場合に止まらないということがありうる。で、この場合は副作用なんかは無い訳で、遅延評価の特性を使っているかどうかをチェックするには、Ocamlでそのまま書いてやればいいことになる。
やってみよう。まず元のソースは以下。

data Tree a = Leaf a | Branch [Tree a]

fringe :: Tree a -> [a]
fringe (Leaf x)    = [x]
fringe (Branch cs) = foldr ((++) . fringe) [] cs

samefringe :: Eq a => Tree a -> Tree a -> Bool
samefringe t u = fringe t == fringe u

これを逐語訳してみる。まず、dataの宣言。

type 'a tree = Leaf of 'a | Blanch of ('a tree) list;;

明らか。ただ、Haskellの方が直感的に見えるのだけども・・・・
で、次にfringeの定義を書く。

let rec fringe = function
         Leaf(x) -> [x]
       | Blanch(x::y) -> (List.append (fringe x) (fringe (Blanch(y))))
       | Blanch([ ]) -> [ ];;

これは単なるマッチング。このマッチング文法は正直、Ocamlの方が綺麗だと思う。で、Haskellのfoldr関数の仕様がよくわからないのだけども、まあ、Haskellではそれぞれにfringeを適用してからリストを結合しているようなので、Ocamlでも同じようなことをやってみた。
そして、最後にその比較を行うわけで

let rec samefringe x y = (fringe x) = (fringe y);;

ということになる。Haskellでは==によってリストが同じかどうかを比較できる。Ocamlの場合は、==はポインタ比較となってしまうので、=で内容比較を行う。
これで完成したOcamlのソースを実行すると・・・・あら不思議・・・でもなんでもなくて、ちゃんと比較できる。

type 'a tree = Leaf of 'a | Blanch of ('a tree) list;;

let rec fringe = function
         Leaf(x) -> [x]
       | Blanch(x::y) -> (List.append (fringe x) (fringe (Blanch(y))))
       | Blanch([ ]) -> [ ];;

let rec samefringe x y = (fringe x) = (fringe y);;


samefringe
     (Blanch [Blanch [Leaf 1; Leaf 2]; Blanch [Leaf 3; Leaf 4; Leaf 5]])
     (Blanch [Blanch [Blanch [Leaf 1]; Leaf 2];
                     Blanch[Leaf 3; Blanch [Leaf 4;Blanch [Leaf 5]]]]);;

ということで、このソースは遅延評価の特性は別に使っていないことが分かる。おしまい。

型の記述に関する議論

OcamlHaskellも、どちらも型推論を搭載している言語である。これは言語の上でとても重要な意味を持っている。だが、上記のソースを見てもらってその型推論が同じに見えるだろうか?一応、やっている内容は同じなのだが。
というのは、Haskellでは基本的に関数の型を自分で記述する。型を記述というのは、例えばCで言うところのint hogehoge(int foo, int bar)みたいなものだ。それに対してOcamlではそれをしない。いや、することが少ないと言っておく。
最初、Ocamlでコーディングしたときには、この型を自分で記述しないことがすばらしい機能だと感じた。というのは、JavaGenericsでそれが嫌だったからだ。JavaGenericsはよい機能なのだが、例えば変数の型宣言でLinkedList<Vector<Integer>>>とか書いていると、気が狂いそうになる。しかも、コンストラクタにもそれを書かなくてはいけなかったりして、タイピング量がひたすらに多い。
で、Ocamlではそれをやらなくてよいのが理想だったのだが(というより、言語実装者のほうもそれを目指している感はある)、なぜHaskellではわざわざ型を書くのか。そして、どちらの方が良いのか。これを考えるために、ちょっと以下のOcamlのソースを見てほしい。

type 'a tree = Leaf of 'a | Blanch of ('a tree) list;;

let rec fringe = function
         Leaf(x) -> x
       | Blanch(x::y) -> (List.append (fringe x) (fringe (Blanch(y))))
       | Blanch([ ]) -> [ ];;

let rec samefringe x y = (fringe x) = (fringe y);;

なんだ上のソースと一緒じゃないか、と思うかもしれない。だが、実はちょっと違うのだ。どこが違うかといえば、fringe関数の2行目、Leaf(x)の返り値がxになっている。正しいソースだと[x]になるはずなのだ。で、これは今日実際に僕がやったバグなのだ。
なんと、このソースは関数自体はエラーを出さない。ただし、関数の型はちょっと変わってしまって、本来であれば

val fringe : 'a tree -> 'a list = 
val samefringe : 'a tree -> 'a tree -> bool = 

という型になるはずが、

val fringe : 'a list tree -> 'a list = 
val samefringe : 'a list tree -> 'a list tree -> bool = 

となっている。この型が違うのは気がついたのだが、どこでこの型が間違っているのかを探すのに非常に苦労した。非常にといってもたかが知れているが、この長さのソースでそんな分からない部分が発生したというのが問題なのだ。
だが、Haskellであれば、どの部分で型が違うというエラーが出る。確かに、関数の型を自分で書くのはめんどくさい。だけどもそのかわりに、どこの型が違うのかというのをきちんと教えてくれる。上記のOcamlのソースの場合はそこまで教えてくれない。関数を適用したときに、"引数の型が違う"とぼやくだけなのだ。関数型言語は関数をどんどんと作っていくのだから、関数を定義したときにその関数のエラーは出力してほしい。なるほど、納得がいく。
Ocamlの人は型を記述しようとしない。文法上できるが、やりやすいとは思わないし、やり方は教えてもらったとはいえ「型は記述しろ」とid:wpwさんに言われた記憶はない(・・・言ってないですよね?)。Haskellの人は、ガリガリ記述する。記述するのが当たり前に見える。これは文化の違いのようなものだとは思うが、しかしながら確かに型を記述するというのはバグ発見の近道だと思う。じゃあJavaにすれば、という意見もあるかもしれないが、Haskellは必要のない型は書かなくても良い。書きたいところだけ書けばいい。つまり、ここに自由度がある。
OcamlHaskellも、自分が型を書きたいところだけ書けばよいというかたちにしてはいるが、デフォルトで書かなくて良いOcamlと基本的に書く方針のHaskell。言語の強さ的にHaskellが最近強いのは、これが一要因になってるような気もする。