Archive for the ‘On newLISP’ Category

newLISP で On Lisp する...第22章(その4)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 今回で、第22章 非決定性 は、終わりです。
 真の非決定性 の 最初は、決定的な検索 のスクリプト。

(define (path node1 node2)
  (bf-path node2 (list (list node1))))

(define (bf-path dest queue)
  (if (null? queue)
      '@
      (letn ((path (first queue))
             (node (first path)))
        (if (= node dest)
            (reverse path)
            (bf-path dest
                     (append (rest queue)
                             (map (lambda (n)
                                    (cons n path))
                                  (neighbors node))))))))

(define (neighbors node)
   (map (curry nth 1) (find-all (list node '?) '((a b) (b c) (c a) (a d) (d e)))))

 関数neighbors は、“On Lisp”本書では、定義されていないのですが、ループのある有向グラフ から類推で作りました。
 引数のnode から行ける先をリストで返します。本章(その1)で定義している関数kids を使って、(defun は、newlisp-utility.lsp に定義してあります)

(defun kids (n)
    (case n
      (a '(b d))
      (b '(c))
      (c '(a))
      (d '(e))))

 としても、同じです。どちらを使うかは、お好みで。ただし、kids を使う場合は、neighbors に関数名を変更することをお忘れなく(笑)。
 動作させると、

> (path 'a 'e)
(a d e)
> (path 'a 'c)
(a b c)
> 

 “On Lisp”本書に動作例がないので比較できませんが、こんなところでしょう。
 さて、上記関数kids で、これまでの 非決定的な検索 を行うと、間違いなく a → b → c → a でループします。
 そのために、Scheme による真の choose が必要となります。
 しかし、残念ながら、“On Lisp”本書には、Common Lisp の実装例は載っていません。
 なので、割愛? いえいえ、そんなことは、ありません(笑)。
 本章(その1)で定義した非決定的オペレータ・マクロ choose-bind で、変更が必要なのは、中で使っている補助関数cb のみです。
 (mklist は、newlisp-utility.lsp に定義してあります)

(define *paths* '())
(define failsym '@)

(define-macro (choose-bind)
; (choose-bind var choices &body body)
  (letex (_var (args 0)
          _choices (args 1)
          _body (cons 'begin (2 (args))))
    (cb (fn (_var) _body) _choices)))

(defun cb (f choices)
  (let (chlist (mklist choices))
    (dolist (ch chlist)
      (letex (_f f _ch ch)
        (push (fn () (_f '_ch)) *paths* -1))))
    (fail))

 これを使って、先ほどの関数kids と本章(その2)で定義した、(=defun と =values は onnewlisp.lsp に定義してあります)

(defun descent (n1 n2)
  (letex (_n1 n1 _n2 n2) 
    (descent-tr '_n1 '_n2 '())))

(=defun descent-tr (n1 n2 res)
    (cond ((= n1 n2) (=values (push n2 res -1)))
          ((kids n1) (letex (_n1 n1)
                       (choose-bind n (kids '_n1)
                           (descent-tr n n2 (push '_n1 res -1)))))
          (true (fail))))

 で実行すると、

> (descent 'a 'c)
(a b c)
> (fail)
@
> (descent 'a 'e)
(a d e)
> 

 無限ループに陥らず、解が出ます。
 ただし、場合によっては、(fail) で、終了シンボル@ にたどり着きません。
 例えば、上記の例に続けて、(fail) を実行すると、

> *paths*
((lambda () ((lambda (n) 
    (begin 
     (letex (_n n) (descent-tr '_n 'e (push 'c '(a b) -1))))) 'a)))
> (fail)
(a b c a d e)
> (fail)
(a b c a b c a d e)
> 

 解としては間違っていないけど、(a d c) の繰り返しが、追加されていくだけ(笑)。
 こういう時、クロージャが全て見える newLISP は、重宝します。何が起こっているのか、一目瞭然です。
 試してはいませんが、“On Lisp”本書にある Scheme による真のchoose でも、同じようになると思うのですが、如何でしょうか?
 “On Lisp” 本書にもあるように、“元の実装で大抵は、十分だ。” ということで、真の非決定性 は、このあたりで終わります。

 さて、番外編から始まった 第22章 非決定性、ようやく、まとめです。

  • newLISPでも、継続渡しを使った非決定性を実装できます。
    しかし、継続 の時と同様に、Common Lispでの実装よりも制限が多くなります。

 以上、如何でしょうか?

newLISP で On Lisp する...第22章(その3)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 さて今回は、第22章 非決定性カット
 “On Lisp” 本書では、Scheme のスクリプトのみで解説が進みます。
 Scheme 似の newLISP にとっては、実装しやすい?
 とは、いっても、継続渡しマクロを暗に使っているので、onnewlisp.lsp は、必須です(同時に、newlisp-utility.lsp も必要です)。
 当たりキャンディー検索の網羅的な例 のスクリプトは、こうなります(choose-bind は、本章(その1)に定義してあります)。

(define (find-boxes)
  (setq *paths* '())
  (choose-bind city '(la ny bos)
    (println)
    (choose-bind store '(1 2)
      (choose-bind box '(1 2)
        (let ((triple (list city store box)))
          (print triple)
          (if (coin? triple)
              (print 'c))
          (fail))))))

(define (coin? x)
  (member x '((la 1 2) (ny 1 1) (bos 2 2))))

 動作させると、

> (find-boxes)

(la 1 1)(la 1 2)c(la 2 1)(la 2 2)
(ny 1 1)c(ny 1 2)(ny 2 1)(ny 2 2)
(bos 1 1)(bos 1 2)(bos 2 1)(bos 2 2)c@
> 

 ここで、letex を使わなくても動作するのは、choose-bind の引数が即値だから。
 そして、検索ツリーのマークと枝刈り のスクリプトは、

(define (mark) (setq *paths* (cons fail *paths*)))

(define (cut)
  (cond ((null? *paths*))
        ((= (first *paths*) fail)
         (setq *paths* (rest *paths*)))
        (true
          (setq *paths* (rest *paths*))
          (cut))))

 となり、これを前述の関数find-boxes に組み込むと、次のようになります。

(define (find-boxes)
  (setq *paths* '())
  (choose-bind city '(la ny bos)
    (mark)
    (println)
    (choose-bind store '(1 2)
      (choose-bind box '(1 2)
      (let ((triple (list city store box)))
        (print triple)
        (when (coin? triple)
           (cut)
           (print 'c))
        (fail))))))

 動作は、

> (find-boxes)

(la 1 1)(la 1 2)c
(ny 1 1)c
(bos 1 1)(bos 1 2)(bos 2 1)(bos 2 2)c@
> 

 すんなり動きます。毎回このように進めば、いいのですが(笑)。
 きりがいいので、真の決定性 からは、次回に。

 以上、如何でしょうか?

newLISP で On Lisp する...第22章(その2)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 “問題が正しく定義できれば、半ば解けた同然である。” と言ったのは、アメリカの教育学者John Dewey氏。
 それはさておき、前回の関数descent は、うまく動作していませんが、バックトラックはうまくいっていることが判っています。
 ただ、全ての再帰の道筋が返ってきているだけ。つまり、関数descent が再帰した分だけ n1 が cons され、返ってくる。
 問題点がわかりました。つまり、必要な分だけ再帰で戻ってきてほしいのです。それが、解決方法。
 そこで考えました、末尾再帰を使えば良いのでは!
 (defun は newlisp-utility.lsp に、=defun と =values は onnewlisp.lsp に、kids と choose-bind は 本章(その1)に定義してあります)

(defun descent (n1 n2)
  (letex (_n1 n1 _n2 n2) 
    (descent-tr '_n1 '_n2 '())))

(=defun descent-tr (n1 n2 res)
    (cond ((= n1 n2) (=values (push n2 res -1)))
          ((kids n1) (letex (_n1 n1)
                       (choose-bind n (kids '_n1)
                           (descent-tr n n2 (push '_n1 res -1)))))
          (true (fail))))

 これなら、戻ってきてほしい要素だけが、末尾再帰関数descent-tr に引数として渡されます。
 こういう時、リストの末尾に追加できるnewLISP組込push が、便利です。

> (descent 'a 'g)
(a c f g)
> (fail)
@
> (descent 'a 'd)
(a b d)
> (fail)
(a c d)
> (fail)
@
> (descent 'a 'f)
(a c f)
> (fail)
@
> 

 うまくいきました!
 ま、でも、Common Lisp や Scheme で試していたら、“うまく言った” で終わって、進歩しなかったでしょうね。newLISP を選んで正解でした(笑)。
 また、前回書き忘れましたが、前の時と違って、“On Lisp”本書のように括弧が一個なのは、今回が多値対応だからです。
 
 さて、letex で渡している変数が、n1 だけですが、全てを記述すると、

(=defun descent-tr (n1 n2 res)
    (cond ((= n1 n2) (=values (push n2 res -1)))
          ((kids n1) (letex (_n1 n1 _n2 n2 _res res)
                       (choose-bind n (kids '_n1)
                         (letex (_n n)
                           (descent-tr '_n '_n2 (push '_n1 '_res -1))))))
          (true (fail))))

 当然、このスクリプトでも動きます。
 と、いうより、こちらが本来の書き方かも(笑)。では、何故前述のスクリプトでも動くのか?
 このスクリプトで n2 は、最後まで変わりませんからね。
 ちなみに、入り口の関数descent は、

(defun descent (x y)
  (descent-tr x y ‘()))

 なら、letex は、いりません。変数 n1、n2 を 変数 x、y に変えてあるのがポイント。つまり、変数補足の問題です。
 これで、Common Lisp での実装 ならぬ newLISP による実装 が完成しました(笑)。

 カット からは、次回に。

 以上、如何でしょうか?

newLISP で On Lisp する...第22章(その1の続き)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 さて、“On Lisp” 第20章の 継続渡しマクロ と共に使った例を(=defun、=values、=bind は onnewlisp.lsp に、choose-bind は 本章(その1)に定義してあります定義してあります)。

(=defun two-numbers ()
  (choose-bind n1 '(0 1 2 3 4 5)
    (letex (_n1 n1)
      (choose-bind n2 '(0 1 2 3 4 5)
        (=values '_n1 n2)))))

(=defun parlor-trick (sum)
  (=bind (n1 n2) (two-numbers)
    (if (= (+ n1 n2) sum)
        (list "the sum of" n1 n2)
      (fail))))

 これを実行すると、

> (parlor-trick 7)
("the sum of" 2 5)

 入れ子も出来ます。(multiple-value-list は onnewlisp.lsp に定義してあります)

> [cmd]
(multiple-value-list
  (choose-bind first-name '(henry william)
    (letex (_first-name first-name)
      (choose-bind last-name '(james higgins)
        (=values '_first-name last-name)))))
[/cmd]
(henry james)
> (multiple-value-list (fail))
(henry higgins)
> (multiple-value-list (fail))
(william james)
> (multiple-value-list (fail))
(william higgins)
> (multiple-value-list (fail))
(@)
> 

 letex を使うのが、お約束です。また、*cont* の初期値の values は、以前は、list で代用していましたが、今回は多値対応を使っています。ですから、multiple-value-list で受けないとリストになりません。
 ここまでは、順調でした。
 つまずいたのは、次のスクリプトdescent 。気分も急降下です(笑)。(kids は最初の方に、t は newlisp-utility.lsp に、kids は 本章(その1)に定義してありますに記述してあります。)

(=defun descent (n1 n2)
    (cond ((= n1 n2) (=values n2))
          ((kids n1) (letex (_n1 n1)
                       (choose-bind n (kids '_n1)
                         ;(println '_n1 " " n)
                         (=bind (p) (descent n n2)
                           (=values (cons '_n1 p))))))
          (t ;(println "fail")
             (fail))))

 これを実行すると、

> (descent 'a 'g)
(a b b a c c f g)
> 

 あれれ、余計なものが!
 コメント・アウトしてある println文を動作させてみると、その様子がわかります。

> (descent 'a 'g)
a b
b d
fail
b e
fail
a c
c d
fail
c f
f g
(a b b a c c f g)
> 

 バック・トラックは、うまくいっているのです。ただ、たどった道のりを全て返しているだけで(悲)。
 これは、ダイナミック・スコープによる変数補足を letex を使って回避した副作用なのです。

 そして、その解決方法は、、、次回に。

 以上、如何でしょうか?

newLISP で On Lisp する...第22章(その1)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 番外編から始まった 第22章 非決定性 の本体の始まりです(笑)。

 最初の 概念 は飛ばして、探索 から。
 まずは手慣らしに、決定的なツリーによる探索 の Schemeスクリプトを newLISP で記述すると、

(define (descent n1 n2)
  (if (= n1 n2)
      (list n2)
      (let (p (try-paths (kids n1) n2))
        (if p (cons n1 p) nil))))

(define (try-paths ns n2)
  (if (null? ns)
      nil
      (or (descent (first ns) n2)
          (try-paths (rest ns) n2))))

(define (kids n)
    (case n
      (a '(b c))
      (b '(d e))
      (c '(d f))
      (f '(g))))

 こんな感じです。ついでに、後で使う関数kids も定義しておきます。
 当然動きます。

> (descent 'a 'g)
(a c f g)
> 

 もちろん本題は、非決定的なツリーによる検索 です。
 newLISP には、Scheme のように call-with-current-continuation が無いので、次の Scheme での実装 を飛ばして、Common Lisp での実装 に入ります。
 そして、Common Lisp による非決定的オペレータ を newLISP で実装します。
 (defun は newlisp-utility.lsp に定義してあります)

(define *paths* '())
(define failsym '@)

(define-macro (choose)
; (choose &rest choices)
  (letex (_choice (args 0)
          _choices (args))
    (if '_choices
       (begin (map (fn (c) (letex (_c c) (push (fn () _c) *paths*)))
                   (reverse (rest '_choices)))
              _choice)
      (fail))))

(define-macro (choose-bind)
; (choose-bind var choices &body body)
  (letex (_var (args 0)
          _choices (args 1)
          _body (cons 'begin (2 (args))))
    (cb (fn (_var) _body) _choices)))

(defun cb (f choices)
  (if choices
      (progn
        (if (cdr choices)
            (letex (_f f _ch choices)
              (push (fn () (cb _f (cdr '_ch)))
                  *paths*)))
        (f (car choices)))
      (fail)))

(defun fail ()
  (if *paths*
      ((pop *paths*))
      failsym))

 さて、動作例を、まずは、choose から、

> (defun do2 (x) (choose (+ x 2) (* x 2) (pow x 2)))
(lambda (x) (choose (+ x 2) (* x 2) (pow x 2)))
> (do2 3)
5
> (fail) 

ERR: value expected in function * : x
called from user defined function fail
> (define *paths* '())
()
> (defun do2 (x) (letex (_x x) (choose (+ _x 2) (* _x 2) (pow _x 2))))
(lambda (x) 
 (letex (_x x) (choose (+ _x 2) (* _x 2) (pow _x 2))))
> (do2 3)
5
> (fail)
6
> (fail)
9
> (fail)
@
> 

 番外編でも書きましたが、newLISP のダイナミック・スコープで、継続を行う時は、newLISP組込letex を使って変数をクロージャに組み込んでおく必要があります。
 そうしないと、最初の関数do2 のように、fail を実行した時、エラーとなります。
 そして、choose-bind の例。

> (choose-bind x '(marrakesh strasbourg vegas) (format "Let's go to %s." (string x)))
"Let's go to marrakesh."
> (fail)
"Let's go to strasbourg."
> (fail)
"Let's go to vegas."
> (fail)
@
> 

 choose のマクロ展開例のスクリプトには、やはり、letex を使います。

(let (x 2)
  (letex (_x x)
  (choose
    (+ _x 1)
    (+ _x 100))))

 例によって、展開式すると、

(if '((+ 2 1) (+ 2 100)) 
 (begin 
  (map (lambda (c) 
    (letex (_c c) (push (lambda () _c) *paths*))) 
   (reverse (rest '((+ 2 1) (+ 2 100))))) 
  (+ 2 1)) 
 (fail))

 変数x をすべて展開して組み込みますから、“On Lisp” 本書例より複雑になります(汗)。
 動作は、

> [cmd]
(let (x 2)
  (letex (_x x)
  (choose
    (+ _x 1)
    (+ _x 100))))
[/cmd]
3
> (fail)
102
> (fail)
@
> 

 同じですが。
 長くなってきたので、継続渡しマクロ と共に使った例は、次回に。

 以上、如何でしょうか?

newLISP で On Lisp する...第22章(番外編)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 第22章 非決定性 の始まり、と言いたいところですが、いきなり番外編です(笑)。
 第22章を開始する前に、継続 の問題点を明確にしておきたいからです。

 一番わかりやすい例が、第20章 最後の Code-Walker と CPS変換 の続渡し版リスト逆転スクリプトでしょう (defun 、car、cdr、null は newlisp-utility.lsp に定義してあります)。

(defun identity (x) x)

(defun rev2 (x)
  (revc x identity))


(defun revc (x k)
  (if (null x)
      (k '())
    (revc (cdr x)
    (letex (_k k
            _x x)
      (lambda (w)
        (_k (append w (list (car '_x)))))))))

 何気なく、newLISP組込letex を使っていますが、これを使わないと動きません。
 単純に “On Lisp” 本書のスクリプトを newLISP用に変換しただけなら、

(defun revc (x k)
  (if (null x)
      (k '())
    (revc (cdr x)
          (lambda (w)
            (k (append w (list (car x))))))))

 で、すむばずですが、これでは、所望の動作をしません。
 何故か?それは次のコードを動かしてみれば判ります。

(defun revc (x k)
  (if (null x)
      (k '())
    (revc (cdr x)
    (letex (_k k
            _x x)
      (lambda (w)
        (println "x=" x " _x=" '_x)
        (_k (append w (list (car '_x)))))))))

 x と _x を出力するようにしたものです。動作させると、

> (rev2 '(a b c))
x=nil _x=(c)
x=nil _x=(b c)
x=nil _x=(a b c)
(c b a)
> 

 見ての通り、x には nil です。 newLISP では、関数recv の引数x と、 lambda 内の x は別物として扱われます。
 “On Lisp” のスクリプトは、CommonLisp のレキシカル・スコープにより同じになることを前提に書いてあります。
 一方、ダイナミック・スコープの newLISP では、別物として扱われています。このため、letex を使って、ローカルな x の値を lambda式内に埋め込む必要があったのです。
 これが、newLISP での継続渡しの制約であり、“On Lisp”本書の継続渡しマクロを使う際、最大の問題となります。

 さて、第22章 非決定性 の本題は次回から(汗)。

 以上、如何でしょうか?

newLISP で On Lisp する...第21章(その2の続き)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 今回は、第21章 マルチプロセス の動作例、黒板を使った同期 からです。

(define *bboard* '())
(defun claim   () (push (args) *bboard*))
(defun unclaim () (replace (args) *bboard*))
(defun check   () (find (args) *bboard*))
(=defun visitor (door)
  (print (format "Approach %s. " (string (term door))))
  (claim 'knock door)
  (wait d (check 'open door)
    (print (format "Enter %s. " (string (term door))))
    (unclaim 'knock door)
    (claim 'inside door)))
(=defun host (door)
  (wait k (check 'knock door)
    (print (format "Open %s. " (string (term door))))
    (claim 'open door)
    (wait g (check 'inside door)
      (print (format "Close %s.\n" (string (term door))))
      (unclaim 'open door))))
(program ballet ()
  (forkx (visitor 'door1) 1)
  (forkx (host 'door1) 1)
  (forkx (visitor 'door2) 1)
  (forkx (host 'door2) 1))

 newLISP組込format を使うとこんな感じのコードになります。
 ballet を実行すると、

> (ballet)
Approach door2. Open door2. Enter door2. Close door2.
Approach door1. Open door1. Enter door1. Close door1.

>>*bboard*
((MAIN:inside MAIN:door1) (MAIN:inside MAIN:door2))
>>(halt)
nil
> 

 といった感じ。所望の動作をしているようです。
 次に関数setpri を使った、優先順位の変更の効果

(=defun capture (city)
  (take city)
  (setpri 1)
  (yield
    (fortify city)))
(=defun plunder (city)
  (loot city)
  (ransom city))
(defun take    (c) (print "Liberating " c ".\n"))
(defun fortify (c) (print "Rebuilding " c ".\n"))
(defun loot    (c) (print "Nationalizing " c ".\n"))
(defun ransom  (c) (print "Refinancing " c ".\n"))
(program barbarians ()
  (forkx (capture 'rome) 100)
  (forkx (plunder 'rome) 98))

 これを、動作させると、

> (barbarians)
Liberating rome.
Nationalizing rome.
Refinancing rome.
Rebuilding rome.

>>(halt)
nil
> 

 となります。
 最後に、関数kill でプロセスを終了させて見ましょう。
 すでに、programped プロセスを生成してある状態で、

> (setq *open-doors* '())
()
> (ped)

>>*procs*
(gensym12)
>>(kill gensym12)

>>*procs*
()
>>(halt)
nil
> 

 といった感じです。
 これで、windows版newLISP でもマルチ・プロセスが使える?(笑)

 さて、第21章 マルチプロセス のまとめです。

  • newLISP でも、“On Lisp” 本書で定義されたようなマルチプロセスを記述できます。

 本来マルチプロセス対応の newLISP が、windows版でも動作するようになってほしいものです(笑)。
 以上、如何でしょうか?

newLISP で On Lisp する...第21章(その2)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 今回は、第21章 マルチプロセス の動作例ですが、その前に前回紹介したコードの修正です。

(define *default-proc*
  (make-proc (state (fn (x)
                        (print "\n>>")
                        (print (eval-string (read-line) 'MAIN))
                        (pick-process)))))

 eval-string文 に、context シンボルのオプションを追加しています。何故かは、後ほど。

 手始めに、マクロforkx から 。前回紹介したコードを動かすには、2010/ 8/24版以降の onnewlisp.lspnewlisp-utility.lsp が必要です(i+ は newlisp-utility.lsp に、=defun は onnewlisp.lsp に定義してあります)。

> [cmd]
(=defun foo (x)
  (print "Foo was called with " x ".\n")
  (=values (i+ x)))
[/cmd]
(lambda (*cont* _x) 
 (letex ((x _x)) 
  (begin 
   (print "Foo was called with " x ".\n") 
   (=values (i+ x)))))
> (forkx (foo 2) 25)
(foo 2)
> *procs*
(gensym12)
> (proc-state gensym12)
(lambda (gensym11) (foo 2) (pick-process))
> (proc-pri gensym12)
25
> 

 *procs* に登録されたプロセスが gensym変数なのは、context版構造体の仕様です。 stateフィールドのラムダ式の最後に (pick-process) がありますから、継続してプロセスが実行されます。デフォルトのプロセス *default-proc* でも、最後に、pick-process を呼び出していますしね。
 次にマクロprogram を

> [cmd]
(program two-foos (a b)
  (forkx (foo a) 25)
  (forkx (foo b) 25)) 
[/cmd]
(lambda (*cont* _a _b) 
 (letex ((a _a) (b _b)) 
  (begin 
   (setq *procs* nil) 
   (begin 
    (forkx (foo a) 25) 
    (forkx (foo b) 25)) 
   (catch 
    (while true 
     (pick-process)) '*halt*) *halt*)))
> (two-foos 1 2)
Foo was called with 2.
Foo was called with 1.

>>(halt)
nil
> 

 優先順位が同じですから、後から追加されたプロセスから実行されます。
 しかし、優先順位を変えて実行すると、

> [cmd]
(program two-foos (a b)
  (forkx (foo a) 25)
  (forkx (foo b) 12)) 
[/cmd]
(lambda (*cont* _a _b) 
 (letex ((a _a) (b _b)) 
  (begin 
   (setq *procs* nil) 
   (begin 
    (forkx (foo a) 25) 
    (forkx (foo b) 12)) 
   (catch 
    (while true 
     (pick-process)) '*halt*) *halt*)))
> (two-foos 1 2)
Foo was called with 1.
Foo was called with 2.

>>(halt)
nil
> 

 優先度の高いプロセスから実行されます。
 ここで、先頭に >> がある行は、pick-process 中でデフォルトのプロセス *default-proc* が動作している状態です。そして、それは、multiple-value-bind 中で動作しています。ところで、multiple-value-bind は、multiple-value-bind の context で定義しています。つまり、デフォルトのプロセス *default-proc* の eval-string 文が multiple-value-bind の context で引数を評価してしまうのです。そのため、冒頭のスクリプト、eval-string が引数の文字列を評価する時に使う context を指定することにした訳です。普通の関数処理なら、問題ないのですが、、、
 さて、言い訳はこれくらいにして、次は、マクロwait の動作、 wait 式を1つ持つプロセス です。

> (define *open-doors* '())
()
> [cmd]
(=defun pedestrian ()
  (wait d (car *open-doors*)
    (print "\nEntering " (term d) ".\n")))
[/cmd]
(lambda (*cont*) 
 (letex () 
  (begin 
   (wait d (car *open-doors*) (print "\nEntering " (term d) ".\n")))))
> (program ped () (forkx (pedestrian) 1))
(lambda (*cont*) 
 (letex () 
  (begin 
   (setq *procs* nil) 
   (begin 
    (forkx (pedestrian) 1)) 
   (catch 
    (while true 
     (pick-process)) '*halt*) *halt*)))
> (ped)

>>(push 'door *open-doors*)
(MAIN:door)
Entering MAIN:door.

>>(halt)
nil
> 

 *open-doors* に要素が入るまで、本体のコードの実行が抑制されます。(MAIN:door) は、push の戻り値ですが、MAIN の context が付いてきているのは、前述の理由です。pedestrian で、format の代わりに使っている newLISP組込printterm を使い、出力から context 部分を取り除いています。

 予定外に長くなってきたので(汗)、黒板を使った同期 からは、次回に。

 以上、如何でしょうか?

newLISP で On Lisp する...第21章(その1)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 第21章 マルチプロセス に入ります。

 実は、newLISP は、マルチプロセス対応なのですが、Linux/Unix バージョンのみの機能です。残念ながら、私が使っている windows バージョンでは、使えません(泣)。それだけに、実装し甲斐がある?(笑)

 最初の プロセスの抽象化 にある、マルチプロセス対応の基本要素は、そのまま適用されます。それらは、実装で見てもらった方が早いでしょう。

 実装 では、まず、プロセスの構造体とその生成 を定義します。とはいっても、newLISP には、構造体がありません。以前定義した構造体マクロを使います。第20章で定義した継続渡しマクロと共に onnewlisp.lsp に追加しました。

(defstruct proc pri state wait)

(define *procs* '())
(define *proc* '())

(define *halt* (gensym))

(define *default-proc*
  (make-proc (state (fn (x)
                        (print "\n>>")
                        (print (eval-string (read-line) 'MAIN))
                        (pick-process)))))

(define-macro (forkx)
; (forkx expr pri)
  (letex (_expr (args 0)
          _pri (args 1)
          _gvar (gensym))
    (push (make-proc
            (state (fn (_gvar)
                       _expr
                       (pick-process)))
            (pri _pri))
          *procs*)
    '_expr))

(define-macro (program)
; (program name args &body body)
  (letex (_name (args 0)
          _args (args 1)
          _body (cons 'begin (2 (args))))
  (=defun _name _args
           (setq *procs* nil)
           _body
      (catch (while true (pick-process)) '*halt*) *halt*)))

 早速、継続渡しマクロが使われます。“On Lisp” 本書と、ほぼ同様な実装です。letex が多用されているとか、nil が 空リストになるとか、defvar は define になるとかありますけど(笑)。
 構造体の表記は、、“newLISP で構造体を使う” を参照して下さい。コロン(:)を使わず、括弧で括るだけですけど(笑)。
 また、マクロ名fork は forkx と改名してあります。 windowsバージョンでは使えませんが、newLISP組込だからです。
 そして、プロセスのスケジューリング の実装は、こうなります。(defun、remove、multiple-value-bind、values、 reference-inversion:setf は newlisp-utility.lsp に定義してあります)

(define setfr reference-inversion:setf) 

(defun pick-process ()
  ;(println "pick process")
  (multiple-value-bind (p val) (most-urgent-process)
    (setq *proc* p
          *procs* (remove p *procs*))
    ((proc-state p) val)))

(defun most-urgent-process ()
  (let ((proc1 *default-proc*) (max1 -1) (val1 t))
    (dolist (p *procs*)
      (let ((pri (proc-pri p)))
        (if (> pri max1)
            (let ((val (or (not (proc-wait p))
                           ((proc-wait p)))))
               (when val
                 (setq proc1 p
                       max1  pri
                       val1  val))))))
    (values proc1 val1)))

(defun arbitrator (test cont)
  (setfr (proc-state *proc*) cont
         (proc-wait *proc*) test)
  (push *proc* *procs*)
  (pick-process))

(define-macro (wait)
; (wait parm test &body body)
  (letex (_parm (args 0)
          _test (args 1)
          _body (cons 'begin (2 (args))))
    (arbitrator (fn () _test)
                (fn (_parm) _body))))

(define-macro (yield)
; (yield &body body)
  (letex (_body (cons 'begin (args))
          _gsym (gensym))
    (arbitrator nil (fn (_gsym) _body))))

(defun setpri (n) (setfr (proc-pri *proc*) n))

(defun halt (val) (throw val))  ; 2008/02/19 corrected

(defun kill (obj)
  (if obj
      (setq *procs* (remove obj *procs*)))
  (pick-process))

 setf の代わりに、reference-inversion:setf を setfr として使っています。構造体定義に context バージョンを使ったのは、これが使えるから。おかげで、こちらも、本書とほぼ同様な定義で済みました。
 さて、気になる動作は、どうなるでしょうか?
 実装だけで長くなってしまったので、動作例は次回に(笑)。

 以上、如何でしょうか?

newLISP で On Lisp する...第20章(その4)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsponnewlisp.lsp に定義してあります。)

 第20章 継続 も4回目。今回で終わります。

 では、新しい 継続渡しマクロを使ったツリーの探索 の実装です。
(=bind、=defun、=value は onnewlisp.lsp に定義してあります)

(setq *saved* '())

(=defun dft-node (tree)
  (cond ((null tree) (restart))
        ((atom tree) (=values tree))
        (true (push (list *cont* (fn () (dft-node (cdr tree))))
                 *saved*)
          (dft-node (first tree)))))

(=defun restart ()
  (if *saved*
      (let (cc (pop *saved*))
        (let (*cont* (cc 0)) ((cc 1))))
    (=values 'done)))

(=defun dft2 (tree)
  (setq *saved* nil)
  (=bind (node) (dft-node tree)
    (cond ((= node 'done) (=values nil))
          (true (print node)
                (restart)))))

 前回と違うのは、dft-node と restart のみです。dft-node で、複数の探索の例 で継続渡しを可能にするために、変数 *cont* も push し、restart で取り出しています。
 単独の探索の結果は前回と同じとなります。

> (setq t1 '(a (b (d h)) (c e (f i) g)) t2 '(1 (2 (3 6 7) 4 5)))
(1 (2 (3 6 7) 4 5))
> (dft2 t1)
abdhcefignil
> (dft2 t2)
1236745nil
> 

 そして、複数の探索の例。*cont* で継続渡しているのはラムダ式だけなので、変数の引渡しも必要です。

> (setq t1 '(a (b (d h)) (c e (f i) g)) t2 '(1 (2 (3 6 7) 4 5)))
(1 (2 (3 6 7) 4 5))
> [cmd]
(=bind (tmp) (dft-node t1)
  (if (= tmp 'done)
      'done
    (begin
      (setq node1 tmp)
      (=bind (node2) (dft-node t2)
        (list node1 node2)))))
[/cmd]
(a 1)
> (restart)
(a 2)
> (time (restart) 60)
6
> (restart)
(g 5)
> (restart)
done
> 

 となります。変数の引き渡しとして、変数 node1 をグローバル変数にしています。継続仕様から見れば、反則技かもしれません。しかし、ダイナミック・スコープの newLISP で、継続渡しをする際の制限です。私の実力かもしれませんが(汗)。

 Code-Walker と CPS変換 では、継続渡し版リスト逆転スクリプトを実装してみます。(defun と null は、newlisp-utility.lspp に定義してあります)

(defun identity (x) x)

(defun rev2 (x)
  (revc x identity))

(defun revc (x k)
  (if (null x)
      (k '())
    (letex (_k k
            _1st (first x))
      (revc (rest x)
            (fn (w) (_k (append w (list '_1st))))))))

 ここでは、letex での変数置換だけで済みます。
 動作は、

> (rev2 '(a b c))
(c b a)
> 

 この通り、継続表現は可能です。

 さて、第20章 継続 のまとめです。

  • newLISPでも、継続渡しを実装できます。しかし、Common Lispでの実装よりも制限が多くなります。

 以上、如何でしょうか?