星田さんの記事に対するコメント。
って事は一度インストールしてしまえば全部パッケージになって入ってるのか!これは便利だ・・Racket最高だな!
まぁ、実際問題、Pythonに比べてもデカい処理系だと思います。
色々ビルトインで揃ってる。
ちなみに例の高校数学をPythonで・・って本も、ちょうど二次方程式のグラフ描画のところだったからタイムリーすぎる!ひょっとして目次ご覧になって差し出してもらってるのか?というタイミング(・。・;
いや、偶然です(笑)。
ただ、遅かれ早かれ「グラフ描画のネタは出てくるだろうな」とは予測はあったですけどね(数学のプログラミングでグラフ描画がない、って方があり得ないでしょ・笑)。
遅延評価の勉強をちょっと。Stream系とLazy系の2つがあるのかな?
いや、ないです(笑)。
反面、Streamって言い方は元々SICPの言い方ですね。
SICP上での自作関数及びマクロ(※1)でstream、と言う言い方が出てきたんですが、「ここで定義されてる関数とマクロは便利なんで仕様に含めた方がイイんじゃね?」ってんでその拡張も含めて提案されたのがSRFI-41なんですよ、平たく言うと。
で、Scheme処理系でSRFI-41をサポートしたり、あるいは本体に一部組み込んだり、ってんでstreamって名称が増えていったんです。
で、こっからが本番や!何でも高階関数で書いてみるチャレンジを試みよう、と。こっからどうやっていじって行くか・・が、どうしても思いつかない(^_^;)。
あ、Procが+ってのは置いといてFoldLを無理やり使ってフィボナッチ数列をひねり出す方法が分からない。前の出力結果が必要になるので再帰だったら分かるけど、Lambdaの定義の中で結果を保存しておいて再利用するってのが、コレほんと3時間くらい考えてたんですけど駄目でした。典型的な時間の無駄だな!
これにはマジでビックリした(笑)。foldlでフィボナッチ数列を書こう、なんて思いつきもせんかったわ(笑)。スゴい。
そうなんだよな、プログラミングやっていくと「今まで学んだ常識」に捕われて、「こういうのにはこういうパターン」ってやっぱアタマが固くなってくんだわ。
Lisp系言語ってそれでも自由度は高いんだけど、やっぱそういう発想の自由さって大事だよな。
うん、これはね。初期値の与え方を工夫すればエエの。必ずしも初期値は数である必要はない。例えばリストなんかでもいいわけ。
そう考えればこんな風に書けます。
(define (fibo4 n)
(car (foldl (lambda (y x)
`(,(cdr x) . ,(+ (car x) (cdr x)))) '(0 . 1) (range n))))
あるいはこう書く、とかね。
(define (fibo4 n)
(car (foldl (lambda (y x)
(cons (cdr x) (+ (car x) (cdr x)))) '(0 . 1) (range n))))
つまり、letrec版とか名前付きlet版を見て参考にして欲しいんだけど。
その2つの末尾再帰版フィボナッチ数列のキモ、ってのは初期値が2つあって、要するに単純に言うと位置をズラしていってるのね。aの位置にbが入ってきて、bの位置には (+ a b) が入ってくる。
この「初期値2つ」をfold傘下でどうすればいいのか、っつーと1つしか初期値がなくてもペアを使う事を考慮する事が出来るわけ。
ここではだから、初期値を '(0 . 1) と言うドットペアにしている。
そうすればラムダ式でどういう演算をすればいいのか、ってのは自ずとから決まるわけだ。なんせ、ラムダ式の「返り値」はどっちにせよ次の「初期値」になる。
そんなワケで、末尾再帰版と同様に、初期値を右から左へとズラしていくようなイメージでラムダ式を書けば、自ずとからfoldl版フィボナッチ数列が仕上がる、とそういう寸法になります(なお、n及びyはカウンターとして働いてるだけ、でフィボナッチ数の「計算」にはまるで関わりがない)。
いやでも、マジでfoldl使ってフィボナッチ数列、なんざ考えた事もなかったんで、いい勉強になりました。ありがとう。
なんかフィボナッチ数の一般項ってのが見つかった。一般項って?判定式として使えるのか?とそのままRacketへ
一般項、ってのは数学的な解です。
例えば1からnまで足した和は何?って言われると、プログラマは、例えばPythonだとフツーはこんな風に書くでしょ?
def summation(n):
acc = 0
for i in range(n + 1):
acc += i
return acc
愚直に題意を分解して、数値を地道に足していく。
ところが、数学者はこんな事はしない。「一般解」とか「一般項」ってのを計算(式変換)して出しちまうんだな。
決して1つづつ足す、なんつーシチメンドクセェ事を数学者はやんない(笑)。まずは紙と鉛筆で計算結果を出すんだ(笑)。
言い換えると彼らは漸化式をまずは解く。ここで出てきた「答え」を一般解、とか一般項と呼ぶわけ(※2)。
つまり、いつか書いたと思うんだけど、同じ漸化式に対する話題でも数学者、フツーのプログラマ、関数型言語プログラマ、オブジェクト指向プログラマは全く態度が違うのね。
- 数学者: 漸化式を見るとまずは紙と鉛筆で解いちゃう。
- フツーのプログラマ: 漸化式を見ると人間コンパイラになって漸化式をバラバラに分解して、「人間コンパイル」結果をコンピュータに丸投げする。
- 関数型言語のプログラマ: 最初から漸化式をコンピュータに丸投げ。何も考えていない(笑)。
- オブジェクト指向のプログラマ: 「まずは数列と言うクラスを作って・・・」と初手から明後日の方向に歩き出す。最後はコンピュータに丸投げするが、コード量はフツーのプログラマが書くコードの2倍から3倍になっている。
んで、いわゆるフィボナッチ数列も漸化式です。数学者がそれを解いちゃったら「一般項」になった、って事です。
つまり、nに任意を値を与えたらもう、そのままn項目のフィボナッチ数が出てくる。ぶっちゃけ、コンピュータに丸投げするようなブツじゃない。そして計算速度は最速になります。
とりあえずまあ、期せずして数学スクリプトで遊ぶってのは達成できたかな!
うん、ホンマ、何か実験するにはサイコーの媒体ですよ。
※1: ところがSICPにはマクロの定義法及び使用法は全く説明されていない。
本の成立時期がR4RS(マクロは付録扱い)の頃だった、って事もあるんだけど、そもそも「使えないモノを使って説明する」って事をSICPはやっちゃうんだよな。恐ろしい事に、そういう不完全さ、と言うか尻切れトンボで一節丸々使っちゃう。
そういう辺りが僕がSICPの評価を高く出来ない理由なんだ。
なお、Schemeでマクロが正式導入されたのはR5RSで、そういう意味では割に最近だ(とは言ってももう20年くらい経つけど)。
※2: 忘れてるかもしれないけど、高校数学の二年生で数列、数列の和、とか言うトピックが出てきてて、そこで一般項やら一般解をやってる筈です。