星田さんの記事に対してのコメント。
まず最初に言い訳させてもらう(笑)。
「あれ、eki_tってどんなデータ型だっけ?」
と(笑)。
全くどんなデータだったか失念してんだよなぁ(苦笑)。
一応、「プログラミングの基礎」のWebページに、「メトロネットワークデータ」ってのが載ってるんだけど、ここに来るまで加工に加工を重ねてるんだろうから、どんなデータになるべきなのか、サッパリ分かっていない(笑)。
一応、OCamlのsaitan_wo_bunri関数の直訳は以下のようなカンジになる筈。
データ概形は分かるけど、具体的なデータは持ってないんでテストが出来ない。よってエラーが起きるかもしんない。
その場合は直して欲しい。
いずれにせよ、星田さんが書いてるヴァージョンだと再帰部分が足りない。単にrestをpとvに分けてるだけで、saitan_wo_bunri関数自体を呼び出してないんだ。
要するにrestをpとvに分けるのではなく、saitan_wo_bunriをrestに適用した結果をpとvに分けないとならない。
Python辺りだと、
p, v = saitan_wo_bunri(rest)
と書く局面だ。
しかし、ここで星田さんを責められない。と言うのも、Schemeの仕様範疇内でこれを簡単に書くのはムズい。
ここで必要なのは、ANSI Common Lispで言うトコのdestructuring-bind、そしてScheme標準外の機能(たぁ言っても、GaucheやRacket、その他のScheme実装にも広く取り入れられている)であるmatch-letだ。
よって、ここは、OCamlから離れればmatch-letの使い方を覚えましょう、と言った趣の課題となる。
Foldが本当に苦手だと思った
そう(笑)?
本当にそう思ってるなら、それは「みんなが通る道」だ。
ぶっちゃけ、reduce/foldは最初意味が分からない。みんなそうなんだよな(笑)。
そうでもなければ、「開眼すれば」これほど便利な関数である筈のreduceが、Pythonでfunctoolsに移動する筈がない(笑)。Pythonだと2.x時代はreduceは何もせんでもそのまま使えたんだよ(笑)。ただ、活用実績が出なかったんでライブラリの方に移動になっちゃったんだ(※1)。
要するにreduce/foldは確かに黒魔術への入口なんだ。そして人は黒魔術の入口に立った時、言い訳して使用を諦めて引き返すタイプと、黒魔術の入口に入っちゃって黒魔術師になる為に数多の試練を受けようとするタイプに分かれる。
それで言うと、星田さんは充分食らいついてて、上手い具合やってると思う。大体、フツーの人ならもっと怖がってんだよな(笑)。俺も嫌だったもの(笑)。いや、マジで。
同じことをFoldでやれ!と。ま〜苦戦中です。まずお手本の(fun first (p, v)てのがLambdaで再現できるのか?ってのが不明。
そう、実は星田さんが予想してる通り、これはfoldがどうの、っつーよりdestructuring-bindの機能そのものを含有してるようなOCaml特有のラムダ式の書式をSchemeに持ってくるにはどうすれば?って問題なんだよな。
言わば、foldの難しさってのは全然関係がないわけ。
まぁ、この辺の書式の組み立て、ってのはさすが、OCamlってのはパターンマッチをやっぱ中心にして設計してるんだな、と思う。とにかくあらゆるケースで簡単に変数の組を分解出来るように用意してる、と言うか。
当然、Schemeの仕様範疇内では同じ事が出来ない。そこで、またもや独自拡張機能に頼る事、とする。
これもデータが無いんで、テストはしてない。エラーが出たら自分で直してみて欲しい。
いずれにせよ、ここでのポイントはmatch-lambdaだ(※2)。ラムダ式の拡張で、引数パターンを分解指定し、それを利用して関数本体が記述出来る。
結果、OCamlのラムダ式にむしろ近いのは、機能的にはmatch-lambdaだ、と言う事が分かる。
lambda式の拡張、及びletの拡張、ってのは割にどのScheme処理系でも力を入れてるトコだ(※3)。
特に、パターンマッチ機能を追加してる処理系だと、何らかのmatch絡みのletやlambdaの拡張はほぼやってる、と考えて間違いないだろう。
いずれにせよ、Racketのmatchの項目とGaucheのmatchの項目は流し読みでもいいからサラッと読んでおいた方がいいだろう。
※1: Python人気の拡大で、Pythonユーザーの中で、結果reduceを理解出来る脳みそを持ってないユーザーが増えたのか、あるいはC言語脳の教育者の悪影響なのか、悩むトコだ。
※2: これに関してもGaucheの説明の方が分かりやすい。
なお、Gaucheのリファレンスマニュアルに掲げられている例示は、RacketでRacketのマニュアル上の記述にフォーマルに従うと、
(map (match-lambda
((list item price-per-lb (list quantity (== 'lbs eq?)))
(cons item (* price-per-lb quantity)))
((list item price-per-lb (list quantity (== 'kg eq?)))
(cons item (* price-per-lb quantity 2.204))))
'((apple 1.23 (1.1 lbs))
(orange 0.68 (1.4 lbs))
(cantaloupe 0.53 (2.1 kg))))
となり、Gaucheでは単純にシンボル'lbsとシンボル'kgと言う「記述」もパターン要素として動作を振り分けてるが、生憎、Racketのパターンマッチャーはそこまで器用ではなく、match-expanderと言うマクロの力を借りないとならない。 => 過去ハマった例
※3: 昔はラムダだけ、でシンプルで良かったモンだが、R7RSでもcase-lambdaが導入されたトコを見ても、この辺の「基本機能の拡張」は今後も発展していくかもしれない。
もう、シンプルな時代には戻れないのだ(笑)。