Lazy,Force,Future
id:amachangさんの遅延評価の話、いい感じですね。
Schemeを実験台に、遅延評価と並列計算の議論をしていた頃があったような。
Schemeは実務用途で利用される機会は残念ながらあまりなくて(頑張れGauche!!)、プログラム言語の学術研究向けたたき台言語としてよく利用されていました。
これは、Schemeがつっこみどころ満載?というか、色んな議論が出来る実験言語として面白いからでしょうきっと。
Schemeのつっこみどころとしては、
- ファーストクラスオブジェクトとしての関数(クロージャ)
- 継続の明確化
- ファーストクラスオブジェクトとしての継続
- 末尾再起の最適化
- GC
など様々あります。上記は当然ながら、Scheme処理系として最低限備えなければいけない要素ですよね。
ほかにも
- 多値(引数いっぱい与えられるんだから返り値もいっぱいあっていいよねってすごくね)
- 式評価のトリガー
なんかもあって、セマンティックスが簡潔かつ明瞭な割に、議論すべきことや、学ぶべきことは非常に多い、エッセンスだらけの言語と言ってもいいです。
今回、id:amachangさんが取り上げているのは、このうち、式評価のトリガーですよね。
Schemeは学術研究で良く取り上げられるだけあって、俺俺Schemeが学術研究と称していろいろ作られたり、セマンティクスを俺俺定義したりする場合が多いです。
当然、式評価のトリガーも俺俺Schemeには色んなバージョンがあります。
普通のSchemeに明示的遅延評価を導入した場合
(define lazy (macro (e) `(lambda () ,e))) (define force (lambda (e) (e))) (define lazy+ (lambda (e1 e2) (+ (force e1) (force e2)))) (lazy+ (lazy (fib 10)) (lazy (fib 20))))
実装はテキトウ。
lazyで明示された式は、forceされるまで評価を遅らせると。
lazyはシンタックスなので、マクロ定義が必要。
define-syntaxはよくわかっていないので、保健的でない?macroというシンタックスがあるとしてlazyを定義。
単にlambdaクロージャでラップすると。
forceはこいつを適用するだけ。
lambdaでラップするので、lazy適用した環境でforce時に評価されることが保証されますね。
ただし、プリミティブも含めて再定義が必要だったりして面倒。
遅延評価が標準の俺俺Scheme
(+ (fib 10) (fib 20))
+の引数は、+が評価され、適用されるまで、評価されない。
プリミティブ+内で評価される。
そんな俺俺Schemeの実装がどっかにあったなぁ。
これがあると、無限ストリームが扱えたりして便利。
でもScheme標準と挙動が異なるので、特に副作用に注意が必要だったり。
だからこそ、純関数型言語には、遅延評価はなじみやすいんですね。
折衷型Scheme
(+ (fib 10) (lazy (fib 20)))
(fib 10)は評価されるが、(fib 20)は評価されない。遅延評価オブジェクトが生成。
+にわたった後に(fib 20)評価。
+内で遅延評価オブジェクトがあったらそこで評価すると。
これはプリミティブの実装が面倒ですね全く。
遅延された評価を実際に評価するタイミング
もいろいろあって、一般的なのは
- 必要になったときに初めて評価する
だと思います。
でも、CPU複数あったらなんかもったいなくね?
って導入されたのがfuture。
(+ (future (fib 10)) (fib 20))
futureされた式はとりあえず評価遅延されます。
しかし、CPUリソースが余ったら、本体プロセスとは別に並列計算しておきます。
+は計算が終了されていればもう足し算できると。
出来てなければ待つと。
遅延評価のタイミングだけとっても
面白いですよね。
そもそも、Schemeの式の評価順は決まってないので、
いきなりfutureする実装があっても間違いじゃないわけですね。
future計算プロセスで捕まえた継続
なんて考えると、まあ面白いなぁと。
大学ではそんな研究というか、勉強をしていました。
あと、並列計算してるときどうやってGCしたら効率がいいかとか。
たぶんそんなことが
JavaやJavaScriptコンパイラに応用されているんでしょうきっと。
そんなぐだぐだいわずに
何かコードかけや>俺。
eVC++とWindowsSDK勉強するかなぁ。
食わず嫌いは良くないと、最近知りつつあります。