いわゆる関数型言語 (LISP なんかも含むので "広義の" と注釈を付けよう) における『リスト』というのはおしなべて連結リストのことであるというのは、ある意味『常識』だと思ってたんだけど、それって実はかなり狭い世界の常識なのかなーと、ここのページを読んでて思ったのである。 要するに、これを読んだ瞬間…
要素数が数万にもなるリストに一個ずつ要素を加えるのに、愚直にケツに追加するやつなんてイネーよ! …… Haskeller 以外は。
などと思ったのね。 まあ、その要素を加える操作が、ごくまれにしか起こらないのであればそれでもアリかも知れないけど、この例みたいに 3 万回も繰り返すようなシチュエーションでは絶対にそんなことしない。 そんなの当たり前でしょ……と思ったんだけど、ちょっと待て、と。
自分が関数型言語にハマる以前のことを思い返してみると、実際のところ関数型言語のリストが単方向連結リストとして実装されてることなんて知らなかった気がする。 まあ、わしの場合は、リストのアタマに追加する構文が特別に用意されてる時点で、その辺に疑問を持って調べたからすぐに気付いたけど (あと、最初に本気でいじったのが OCaml で、こいつにはリストとは別に配列があるから気付きやすいというのもあるかも)、人によってはあまり疑問を持たずに配列と同じものだと思って進んでしまう場合もあるかもしれない。 ましてや、配列のことをリストと呼ぶような言語も存在するし (某 P 言語とか)、その辺から入ってくるとますます混乱するだろう。
なんか、わしもすっかり関数型言語脳に冒されているのかなーと、そんなことを思ったできごとであった。
つい何年か前までは「fold って何?うまいの?」とか言ってたわしだけど、すっかり関数型脳になった今では fold が無い生活なんて考えられない。
かつて、わしが最初に fold の使い方を知ったときの例は sum だった。 要するにこんな↓の (コード例は昨今のブームを反映して Erlang)
1> lists:foldl( 1> fun(A,B) -> 1> A + B 1> end, 1> 0, [1,2,3,4,5,6,7,8,9,10]). 55
でもね、はっきり言って、こんな例見せられても fold の醍醐味は絶対理解できない。 fold というのは関数型言語にとっての for ループなのだ。 つまり (例は Java)、
int[] a = {1,2,3,4,5,6,7,8,9,10};
int buf = 0;
for (int i = 0; i < a.length; i++) {
buf += a[i];
}
こんな感じのものと上記の fold の例は対応すると言っても良い。 もう少し一般化して言うと、
fold というのは、変化する "状態" を保持したままループを回す機能である。
と言えるかも知れない。 上の Java の例で言うと、buf が状態である。 なんかカッコつけて書きすぎたが、簡単に言えば、C やその類いの言語で変数を破壊的に操作するようなシチュエーションは、大抵の場合 fold を使って再現することができるということだ。
だから、昨日のネタでもあるけど、檜山さんのとこからコピペ↓して利用させてもらうと、
sample() ->
D0 = dict:new(),
D1 = dict:store("板東トン吉", [27, male], D0),
D2 = dict:store("大垣ペケ子", [21, female], D1),
Val = dict:find("板東トン吉", D2),
Val.
こういうシチュエーションは、
てな感じの論理展開によって、結果昨日の例のように、
1> D = lists:foldl(fun({Name,Age,Sex}, Dict) ->
1> dict:store(Name, [Age,Sex], Dict)
1> end,
1> dict:new(),
1> [ {"ton",27,male}, {"peke",21,female} ]).
こんな感じに変化するのである。 この例では foldl の第一引数に渡している関数の Dict という引数が破壊的に変更する変数にあたる。 んで、第二引数の dict:new() は Dict の初期値。 イメージとしては、リストの中身を順番に関数に渡していって、その都度結果で Dict の中身を書き換えていく感じ。
関数型脳においては、共通した処理を見つけたら、まず関数化するというのが基本パターンのような気がするけど、それと同時に上記のような『同じ関数の呼び出し』を見つけたら、まずその引数 (処理対象) をリストにしてしまって、後でまとめて処理するというのもパターンじゃないかと思う。 なんか、この辺の考え方が、手続き型から関数型に移行する際のブレークスルーな気がするね。
今回の目玉はやっぱ ocamlbuild かね。 と言いつつ、どんな感じのものなのかさっぱりわかってないんだけど。 caml-list で色々議論してたのは眺めてたけど、中身を全然読んでなかったからなー(ほんとに眺めてただけかい)。 とりあえず、三行でまとめると、
てな感じなのかね。 や、まだ使ってみたこと無いんでよくわかんないけどさ。
つーか、メジャーバージョンアップしたときには常に悩むことになるんだけど、通常 GODI で OCaml を入れている人間としては、GODI が対応するのをおとなしく待つべきか、それともとっとと入れてみて遊ぶべきか、あーもーどーしたら。
いや、入れる。今回はとりあえず手で入れる。 ocamlbuild 触ってみたいし。 っていうか、この前 ocamlbuild のために current を入れようと思ったら、なぜかビルドできなかったんだよなー。 さすがにリリース版でビルドできないってことは無いだろうし、今度こそーー。
結局 ppc64 な環境は自動じゃ見っけてくれなくて、./configure -cc "gcc -arch ppc64" とかせにゃならんのだなー。 だとすると GODI 版も 32bit 版でインストールされちゃうのかねぇ。 それはちとせつないな。
……などというグチは置いといて、ocamlbuild を使ってみますよーと。 まず適当なディレクトリ掘って、適当にファイルを用意。
% cat main.ml let main () = Hoge.print (); Fuga.print () let () = if not !Sys.interactive then main () % cat hoge.ml let print () = print_endline "hoge" % cat fuga.ml let print () = print_endline "fuga"
% ls -A fuga.ml hoge.ml main.ml
……で。
% ocamlbuild main.byte Finished, 7 targets (0 cached) in 00:00:01. % ls -FA _build/ _log fuga.ml hoge.ml main.byte@ main.ml % ls -l main.byte lrwxr-xr-x 1 jijixi jijixi 16 5 18 19:22 main.byte -> _build/main.byte % ./main.byte hoge fuga
ちょwww 何これwww 簡単すぎwwww
もうなんつーか、今までの苦労って一体何だったのって感じだよね。 なんか笑うしかない。 例えばここで、main.ml をライブラリにしたいときは…
% ocamlbuild main.cma Finished, 7 targets (6 cached) in 00:00:00. % ocaml -I _build main.cma # Main.main();; hoge fuga - : unit = ()
ちなみに、
% ocamlbuild -clean
で、お掃除。 あと、ナニゲにステキすぎると思う機能。 まず、さっきの三つの ml ファイルを foo というディレクトリ掘って突っ込む。 んで、こんなファイルを用意する。
% cat foo.mlpack Main Hoge Fuga
結果、こんな感じの構成になってる状態。
% ll -l total 4 drwxr-xr-x 5 jijixi jijixi 170 5 18 19:48 foo -rw-r--r-- 1 jijixi jijixi 15 5 18 19:50 foo.mlpack % ls foo fuga.ml hoge.ml main.ml
で、
% ocamlbuild foo.cma Finished, 8 targets (0 cached) in 00:00:00.
% ocaml -I _build foo.cma
Objective Caml version 3.10.0
# module M = Foo;;
module M :
sig
module Fuga : sig val print : unit -> unit end
module Hoge : sig val print : unit -> unit end
module Main : sig val main : unit -> unit end
end
ヤバい、もう鼻血出そう。
あとはインストールなんかもやってくれるようになると、ほんとに make とかいらなくなるんだが、その辺はどうなるんだろう。 別に ocamlfind との連携とかでも良いけど。 一応、ocamlbuild -help とかやると、
-install-lib-dir <path> Set the install library directory -install-bin-dir <path> Set the install binary directory -where Display the install library directory
こんなのはあるけど、実際にインストールするためのターゲットとかは見当たらないっぽい。 逆に ocamlfind の方で ocamlbuild 対応してくるって流れとかあるのか?
えーと、この話に至るまでの流れはツッコミ欄参照。
てことで、調べてみたんだけど、どうもハッキリしないというかよくわからない。 こうなると通常は「ソース読むか」となるんだが、以前にちょろっと覗いたときのトラウマがあって、正直読むのは勘弁していただきたい(苦笑
んで、適当にググっていくつか解説ページとか眺めた感じだと、リストが変数に代入されると、それはもう配列だと言うじゃない。 つーことは、リストというものが存在できるのは名前が付いていないときだけだということになる。 例えば、もしリストが連結リストとして実装されていたとしても、名前を付けたら配列になっちゃうってことは、連結リストとしての存在意義は無いってことで、要はリストがどんなデータ構造かを云々するのは意味の無いことなのかも。
リストとは何かの値がリストコンテキストで評価されるときに、一瞬だけ存在する蜃気楼のようなものなのかもしれない。 ……などと、ちょっと気取った言い方をしてみたり。 コンテキストなどという、わけのわからん文学的な代物が存在する Perl にはお似合いじゃなかろうか。
……すんません、ちょっとやさぐれてしまいました。 ほんと、わしにとって Perl は性に合わんです。 文脈によって同じ変数でも返す値が違うとか、正気ですか? まあ、こんな不思議な言語を考えたラリーウォールは、ある意味天才ですね。
や、これはほんとバカにしてるんじゃなくて、本気でそう思う。 好き嫌いは別にして (残念ながら、わしは好きにはなれないが)、Perl には何か不思議な魅力があるのは確かだと思うんで。
某P言語の話ですけど、リストと配列は見た目は一見同じですが、実は微妙に扱いが違います。<br>ただ、だからといってそのリストがjijixiさんの考えるところの定義の元でのリストかどうかはこれまた微妙ですが。
そういや通常のリストとは別に array モジュールなんてのがあるんでしたっけ。<br>でもあれって、C 言語の不便だけど効率の良い配列を再現するものであって、リストはやっぱり Ruby とか Perl とかで言うところの配列ですよね。<br>まあ、リストと聞いて cons セルだと思っちゃうのは関数型脳の悪影響なので、あれをリストと呼ぶのはけしからんとか言うつもりは無いです。
なんか返事を書いてから微妙に話が噛みあってない気がしたんで調べてみたんですけど、蛇じゃない方の某 P 言語にも『リスト』って名前のものがあるんですね。<br>わしが想定してたのは蛇な方の P でした(苦笑<br>こっちのリストがどんな実装なのかは、もうちょっと調べてみます(汗
えっと、Perlのリストってのはスタック上に存在する配列だ、ってのが近いかも。はい、ある意味蜃気楼です。んでその蜃気楼から通常の配列と同じくインデックスで要素がとってこれたりするわけです。
ふむふむ。そうするとやっぱ、データ構造としては配列と同じようなもんなんですかね。<br>ネストしたリストは自動的に平たく展開されるとか、不可思議な現象は起こるみたいですけど。
あー別のP言語の話だったとは。<br>ところでタイムリーにも、弾さんの今日のエントリ<br>http://blog.livedoor.jp/dankogai/archives/50834465.html<br>に、Perlのリストと配列の話がありまする。