nucさんのcall/ccクイズ
最近はずっとやる気がない。
かなり前に、
これに挑戦して、途中まで解いたメモが残っていたので、一応、最後まで解いてみた。
(以下、自力で解いてみたい人はネタバレ注意)
まず、以下の基本形から始める。
(define a (lambda () ...))
この状態で、(a)を何度呼んでも、副作用が無いので、(a)は常に同じ値を返すしかない。
そこで、とりあえずcall/ccを導入してみる。
(define a (call/cc (lambda (goto-define-a) (lambda () ...))))
これで、(a)を実行した時に、(a)の内部で(goto-define-a val)が実行される事で、(define a ...)の継続に戻れるようになった。
しかし、ここに戻った場合、返される値は「(define a ...)」の結果になってしまい、任意の値を返す事ができない。
call/ccの位置をどう変更しても、一番外側に(define a ...)がある限り、返り値は変更できないが、(define a ...)を実行しなくては、aの束縛を変更する事もできない。
もう少し、工夫が必要なようだ。
(begin (define a (call/cc (lambda (goto-define-a) (lambda () ...)))) ...)
これで、(define a ...)を実行後に、任意の値を返せるようになった。
defineはトップレベル束縛を操作できなくてはいけないので、begin以外では入れ子にする事はできないので、おそらく、この形以外には無いと思う。
どうも、二番目の問題の方が簡単そうなので、先にそっちに挑戦してみる。
(begin (define a (call/cc (lambda (goto-define-a) ;; this is binded to 'a (lambda args (if (null? args) (goto-define-a 1) #t))))) ; dummy return value of (a #t) (if (number? a) 0 ; return value of (a) (a #t)))
綺麗とは言えないが、一応、二番目の問題の解が完成した。
しかし、これは要するに、defineによってaを再定義している訳で、set!と類似の技法と言わざるを得ない。
とりあえず、さっきの解を応用して、一番目の問題も解くだけ解いてみる。
(begin ;; (counter) => get saved number ;; (counter 123) => save number and goto backtrack point (define counter (call/cc (lambda (goto-define-counter) (letrec ((counter-maker (lambda (init-num) ;; this is true counter (lambda opt-new-num (if (null? opt-new-num) init-num (goto-define-counter (counter-maker (car opt-new-num)))))))) (counter-maker -1))))) ; initial number is -1 (define old-count (counter)) (define (a) (counter (+ old-count 1))) old-count)
実に手続き的だと言わざるを得ない。
また、内部でletrecを使ってしまっている。これはちょっと前提条件の「!を使わない」に引っかかる可能性がある(もしletを使ってletrecを実装してみろと言われた時に、どうも、set!無しには実装できないような気がするので)。
もう少しエレガントに解く方法がありそうに思えて、結構な期間考えてみたが、他の方法では微妙に上手く行かなかった。
尚、上の回答は「REPLから呼び出される事」を前提条件としている。
REPL以外から呼び出されても大丈夫にするには、Kahuaで使われている部分継続のようなテクニックを使う必要がある……が、今回は面倒なのでこのままにしておく。