きくらげ観察日記

好きなことを、適当に。

Gaucheに継続を「実装」する

http://inkar-us-i.hatenablog.com/entry/2016/09/01/200000inkar-us-i.hatenablog.com

上の記事でしっかりしたモナドが作れたので、これを使って継続を実装します。

(define (cont-return x)
  (^(cont) (cont x)))

(define (cont-bind x f)
  (^(cont)
    (x (^(xval)
         ((f xval) cont)))))

(define cont-monad
  (make <monad>
    :return cont-return
    :bind cont-bind))

Contモナドは暗黙的なCPS変換を行うモナドです。暗黙引数?monadにcont-monadが与えられた場合、(mreturn x)は継続を引数に受け取ってその継続にxを渡す関数となります。

次に、call/ccに相当する関数を作ってみます。

(define (mcall/cc handler)
  (^(cont)
    (handler cont)))

簡単ですね。Contモナドの中ではすべての式はCPS変換されているので、貰った継続をそのまま渡せばOKです。

実行例は以下の通り。

(define *mcont* #f)
(let ((?monad cont-monad))
  ((mlet*
    ((result
      (mcall/cc
       (^(cont)
         (set! *mcont* cont)
         (cont "hoge")))))
    (mreturn result))
   identity ; 最後に「トップレベルへの継続」に相当するものを渡す
   ))
;; => "hoge"

(*mcont* "fuga") ; 保存した継続は後から呼び出せる
;; => "fuga"

もちろん、(mlet* ..)の結果を保存しておいて、後で継続とともに呼び出すこともできます。

(define *mcont2* #f)
(define *mcont3* #f)
(define proc (let ((?monad cont-monad))
  (mlet*
   ((x (mcall/cc
        (^(cont)
          (set! *mcont2* cont)
          (cont "hoge"))))
    (y (mcall/cc
        (^(cont)
          (set! *mcont3* cont)
          (cont "fuga")))))
   (mreturn (format #f "~s: ~s" x y)))))

(proc print)     ; => "hoge": "fuga"
(*mcont2* "foo") ; => "foo": "fuga"
(*mcont3* "bar") ; => "foo": "bar"