Archive for the ‘make-adder’ Tag

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

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
On Lisp 第2章 関数 も その3 まで来ました。
さて、今回は スコープ です。これも難題です。
Common Lisp は、レキシカル・スコープです。しかし、newLISP は、
(defun は newlisp-utility.lsp に定義してあります)

> [cmd]
(let ((y 7))
  (defun scope-test (x)
    (list x y)))
[/cmd]
(lambda (x) (list x y))
> (let ((y 5)) (scope-test 3))
(3 5) 

ご覧の通り、ダイナミック・スコープです。
この例で使っている [cmd] と [/cmd] は、端末(コンソール)で複数行を記述する時のタグです。
“On Lisp” によれば、レキシカル・スコープは、バグを回避する以上に、新しいプログラミング技法を可能にするもの。私もその意見に賛成です。
では、何故、ダイナミック・スコープの newLISP を選ぶのか?
その答えは、もちろん、context です。実際、そうなのか? 見ていきましょう。

> [cmd]
(context 'MAIN:scope-test)
  (setq y 7)
  (defun scope-test:scope-test (x)
    (list x y))
(context MAIN)
[/cmd]
scope-test
7
(lambda (x) (list x y))
MAIN
> (let ((y 5)) (scope-test 3))
(3 7)

このように、context を使えば、レキシカル・スコープ風の実装ができます。
この実装では、context の scope-test が、defun を囲っている let に相当します。
続いて、クロージャ。最初の例題は、さほど問題ありません。

(defun list+ (lst n)
  (map (fn (x) (+ x n))
       lst)) 

今まで見てきた変更点(mapcar は map、lambda は fn、#’ を外す)を適用しただけです。動作も同じです。

> (list+ '(1 2 3) 10)
(11 12 13) 

しかし、次の例題は、そのままでは使えません。レキシカル・スコープが前提ですから。
ここでも、context の登場です。

(context 'MAIN:id)
  (setq counter 0)
  (defun id:new () (++ counter))
  (defun id:reset () (setq counter 0))
(context MAIN)

動作はというと、

> (id:new)
1
> (id:new)
2
> (id:new)
3
> (id:reset)
0
> (id:new)
1
> (id:new)
2 

こんな感じです。
つまり、context を使うことで、レキシカル・スコープ同等の記述が可能になります。
関数名は違いますが、それは本質ではありません。もし、同じ関数名がお望みなら、

> (define reset-id id:reset)
(lambda () (setq id:counter 0))
> (define new-id id:new)
(lambda () (++ id:counter))
> (new-id)
3
> (new-id)
4
> (reset-id)
0
> (new-id)
1

こんな具合です(笑)。
さて、次の関数make-adder は、

(defun make-adder (n)
  (letex ((nn n))
    (lambda (x) (+ x nn)))) 

動作はというと、

> (setq add2 (make-adder 2) add10 (make-adder 10))
(lambda (x) (+ x 10))
> add2
(lambda (x) (+ x 2))
> (add2 5)
7
> (add10 3)
13 

もちろん、funcall は要りません。組込関数letex を使うことで、レキシカル・スコープでない点をカバーしています。実のところ、make-adder を作る手段はこの他にもあります
しかし、それでは、本章に似合いません。ここは context を使って、引数をローカルに捕捉して見せましょう。
(関数gensym は、Common Lispでは標準ですが、newLISP の組込関数ではありません。newlisp-utility.lsp に定義してあります。)

(defun make-adder (x)
  (let (ctx (gensym))
    (context ctx '_value x)
    (letex (y ctx)
      (fn (x) (+ x (y "value"))))))

関数gensym で生成したシンボル名の context に 変数_value を作成し、引数x を保存します。
変数_value と文字列 “value” の関係は、こちらこのあたりを参照して下さい。
では、実際の動作させて見ましょう。

> (setq add2 (make-adder 2) add10 (make-adder 10))
(lambda (x) (+ x (gensym12 "value")))
> add2
(lambda (x) (+ x (gensym11 "value")))
> (add2 5)
7
> (add10 3)
13

ざっと、こんなもんです。ここまで出来れば、次の内部状態を変化させられるクロージャも平気です。

(defun make-adderb (x)
  (let (ctx (gensym))
    (context ctx '_value x)
    (letex (y ctx)
      (fn (x change)
          (if change
              (y "value" x)
            (+ x (y "value")))))))

動作はというと、

> (setq addx (make-adderb 1))
(lambda (x change) 
 (if change 
  (gensym13 "value" x) 
  (+ x (gensym13 "value"))))
> (addx 3)
4
> (addx 100 t)
100
> (addx 3)
103

データオブジェクトを共有するクロージャのグループを返す関数make-dbms だって目じゃありません。

(defun make-dbms (db)
  (let (ctx (gensym))
    (context ctx ctx db)
    (letex (y ctx)
      (list
        (fn (key)
          (lookup key y))
        (fn (key val)
          (push (cons key val) y)
           key)
        (fn (key)
          (replace (assoc key y) y) key)))))

関数lookup は newLISP では組込関数です。また、newLISP の組込delete は Common Lisp のものと機能が違うので、組込replace に置き換えています。
動作はこうなります。

> (setq city '((boston us) (paris france)))
((boston us) (paris france))
> (setq citis (make-dbms city))
((lambda (key) (lookup key gensym18)) (lambda (key val) (push (cons key val) gensym18) 
  key) 
 (lambda (key) (replace (assoc key gensym18) gensym18) key))
> ((citis 0) 'boston)
us
> ((citis 1) 'london 'england)
london
> ((citis 0) 'london)
england
> ((citis 0) 'paris)
france
> ((citis 2) 'paris)
paris
> ((citis 0) 'paris)
nil

newLISP には、いわゆるドット対がありませんので、連想リスト表記が違います。ご注意を。
setf は使っていませんが、On Lisp の例題も、setf しなくもよいはず。使っている関数delete が、破壊的関数ですから。少なくとも、xyzzy ではそうです。
クロージャの呼び出しには、インデックス機能が使えます。こればっかりは、newLISP が有利ですね(笑)。
もちろん、

> ((car citis) 'boston)
us
> ((second citis) 'newyork 'us)
newyork
> ((car citis) 'newyork)
us
> ((third citis) 'newyork)
newyork
> ((car citis) 'newyork)
nil

という風にも使えます。
と言っても、Common Lisp の car は、newLISP の first で、second と third は、newLISP にありません。
car、second、third は、newlisp-utility.lsp に定義してあります。
またまた、長くなってしまったので、残りの ローカル関数 からは、次回に。
次回で、第2章も終わるでしょう(汗)。

以上、如何でしょうか?