Archive for 2010年6月17日|Daily archive page

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

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
前回に引き続いて On Lisp 第2章 関数
今回は、属性としての関数 からです。のっけから難物です(笑)。
case 文は、newLISPでも使えます。defun に newlisp-utility.lspのマクロを使えば、本文通り記述できます。

(defun behave (animal)
  (case animal
    (dog (wag-tail)
         (bark))
    (rat (scurry)
         (squeak))
    (cat (rub-legs)
         (scratch-carpet))))

試しに、wag-tail と bark を定義して試すと、

> (defun wag-tail ()  (print "wag-tail "))
(lambda () (print "wag-tail "))
> (defun bark ()  (print "bark "))
(lambda () (print "bark "))
> (behave 'dog)
wag-tail bark "bark " 

となります。ここまでは良いのですが、残念ながら、newLISPには属性はありません。したがって、属性を使った関数behave は、定義できません。
とはいっても、それで終わらないのが、newLISPです。
そう、newLISPには context があります。context を使えば、同様の実装ができます。

> (new 'Tree 'dog)
dog
> (dog "behavior" (fn () (wag-tail) (bark)))
(lambda () (wag-tail) (bark))
> (dog "behavior")
(lambda () (wag-tail) (bark)) 

前に紹介した context の使い方です。このように、関数もセットできます。前回見たように関数をデータオブジェクトとして扱えるのですから当然ですね。関数の呼び出しに、funcall は要りません。

> ((dog "behavior"))
wag-tail bark "bark " 

ということで、Common Lisp の属性を使った behave は、newLISP では context を使って、こうなります。

(defun behave (animal)
  ((animal "behavior")))

setf も使えます。

> (setf (dog "behavior") (fn () (wag-tail)))
(lambda () (wag-tail)) 
> (dog "behavior")
(lambda () (wag-tail)) 

context を予め定義する必要があるとか、キーとなるシンボルに "" を付ける(文字列として与える)必要があるとか、違いはありますが、気になるなら、マクロを組めばよいことですよね(笑)。
では、Common Lisp のように関数get を用意してみましょう。

(define-macro (get)
  (letex (_ctx (context (args 0))
          _key (term (args 1)))
    (_ctx _key)))

こんな感じでしょうか?
動作はというと

> (dog "behavior" (fn () (wag-tail) (bark)))
(lambda () (wag-tail) (bark))
> (get dog behavior)
(lambda () (wag-tail) (bark))

うまく行っているようです。クォートが付いていないことは、気にしないで下さい。後で説明します。
しかし、

> (setf (get dog behavior) (lambda () (wag-tail)))
(lambda () (wag-tail))
> (get dog behavior)
(lambda () (wag-tail) (bark))

setf でうまくセットされません。Common Lisp のマクロは、マクロ展開した後、マクロを呼び出したところに戻って評価されますが、newLISP の場合は、マクロ展開した後、マクロを呼び出したところの環境を使って、マクロ内で評価されます。この違いが、ここでは致命的です。
では、newLISP では、関数get はできない?結論からいうとできます。V10.1.6 から導入されたモジュール macro.lsp を使います。

(module "macro.lsp")
(macro (getm C K)
  (C (term K)))

これで、getm を定義して、試してみると、

> (get dog behavior)
(lambda () (wag-tail) (bark))
> (getm dog 'behavior)
(lambda () (wag-tail) (bark))
> (setf (getm dog 'behavior) (lambda () (wag-tail)))
(lambda () (wag-tail))
> (getm dog 'behavior)
(lambda () (wag-tail))
> (get dog behavior)
(lambda () (wag-tail))

この通り、setf も使えます。macro は、V10.1.6 から組み込まれた reader-event を使っていて、定義した内容を newLISP インタープリタがコードを評価する前に展開します。つまり、Common Lisp のマクロのように、展開式をマクロを呼び出したところに展開し、その後評価されるわけです。
これで、前述の関数behave も、

(defun behave (animal)
  ((getm animal 'behavior)))

と定義できます。

> (behave dog)
wag-tail "wag-tail "

動作もこの通り(笑)。さて、getm と baehave の第一引数にクォートがついていません。
これは第一引数が context だからです。
Common Lisp のようにクォートを付けた引数を取る get と behave の最終スクリプトは、

(macro (get C K)
  ((eval C) (term K)))
(defun behave (animal)
  (let (f (getm animal 'behavior))
    (if f (f) ((eval animal) "behavior" '()))))

となり、動作は、

> (get 'dog 'behavior)
nil
> (behave 'dog)
()
> (get 'dog 'behavior)
()
> (setf (get 'dog 'behavior) (fn () (wag-tail) (bark)))
(lambda () (wag-tail) (bark))
> (get 'dog 'behavior)
(lambda () (wag-tail) (bark))
> (behave 'dog)
wag-tail bark "bark "

こんな感じです。
関数behave のちょっとした細工は、context を予め定義しておかない時に発生するエラーの予防です。

さて、前の“On newLISP”では定義しなかった関数 get ができたところで、残り スコープ からは次回に。

以上、如何でしょうか?