Archive for 2010年7月5日|Daily archive page

newLISP で On Lisp する...第6章

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

 第6章 表現としての関数 です。
 まずは、ネットワーク から。本書によれば、ここでの例題は、どのプログラミングでも書けるものとか。それでは、newLISP組込関数だけで書いてみましょう。

(new Tree '*nodes*)
(define *node* '((contents nil)(yes nil)(no nil)))
(define (defnode name conts yes no)
  (let (key (string name))
    (if-not (*nodes* key) (*nodes* key *node*))
    (setf (lookup 'contents (*nodes* key)) conts)
    (setf (lookup 'yes (*nodes* key)) yes)
    (setf (lookup 'no (*nodes* key)) no)))
(defnode 'people "Is the person a man?" 'male 'female)
(defnode 'male "Is he living?" 'liveman 'deadman)
(defnode 'deadman "Was he American?" 'us 'them)
(defnode 'us "Is he on a coin?" 'coin 'cidence)
(defnode 'coin "Is the coin a penny?" 'penny 'coins)
(defnode 'penny 'lincoln)
;(defnode 'coins 'end)
(define (run-node name)
  (let (n (*nodes* (string name)))
    (cond ((lookup 'yes n)
           (print (lookup 'contents n) "\n>> ")
           (case (read-line)
             ("yes" (run-node (lookup 'yes n)))
             (true  (run-node (lookup 'no  n)))))
          (true (lookup 'contents n)))))

 newLISP にはhash がないので、context を hash-like に使っています。動作は、“On Lisp” 本書と同じように入力して下さい。入力ミスは、“On Lisp” 本書のスクリプト同じく、エラーを起こします(笑)。

> (run-node 'people)
Is the person a man?
>> yes
Is he living?
>> no
Was he American?
>> yes
Is he on a coin?
>> yes
Is the coin a penny?
>> yes
lincoln
> 

 そして、ネットワークのコンパイル 。既に、クロージャを返す関数を表現できると判っている newLISP に、問題があろうはずがありません。
 しかし、ここは、make-hash-table と gethash を用意して実装してみましょう。といっても、ハッシュ・テーブルを用意するわけではありません。あくまでも、context による hash-like を使っての実装です。macro も使っています。

(module "macro.lsp")
(define (make-hash-table)
  (new Tree (gensym)))
(macro (gethash K T V)
  (if V 
      (T (string K) V)
    (T (string K))))

 そして、クロージャへとコンパイルされるネットワーク を実装します(defun は newlisp-utility.lsp に定義してあります)。

(setq *nodes2* (make-hash-table))
(defun defnode2 (name conts yes no)
  (let (key (string name))
    (if-not (*nodes2* key) (*nodes2* key ""))
    (letex (_conts conts _yes yes _no no)
      (setf (gethash name *nodes2*)
            (if yes
                (fn ()
                  (print _conts "\n>> ")
                  (case (read-line)
                    ("yes" ((gethash '_yes *nodes2*)))
                    (true  ((gethash '_no  *nodes2*)))))
              (fn () '_conts))))))

(defnode2 'people "Is the person a man?" 'male 'female)
(defnode2 'male "Is he living?" 'liveman 'deadman)
(defnode2 'deadman "Was he American?" 'us 'them)
(defnode2 'us "Is he on a coin?" 'coin 'cidence)
(defnode2 'coin "Is the coin a penny?" 'penny 'coins)
(defnode2 'penny 'lincoln)

 動作に、funcall は要りません。

> ((gethash 'people *nodes2*))
Is the person a man?
>> yes
Is he living?
>> no
Was he American?
>> yes
Is he on a coin?
>> yes
Is the coin a penny?
>> yes
lincoln
> 

 そして、本命、静的参照によるコンパイル の実装です(null、second、third、fourth は newlisp-utility.lsp に定義してあります)。

(define *nodes3*)
(defun defnode3 ()
  (push (args) *nodes3* -1)
  (args))
(defun compile-net (root)
  (let (node (assoc root *nodes3*))
    (if (null node)
        nil
      (let (conts (second node)
            yes   (third node)
            no    (fourth node))
        (if yes
            (letex (yes-fn (compile-net yes)
                     no-fn (compile-net no)
                    _conts  conts)
              (fn ()
                (print _conts "\n>> ")
                ((if (= (read-line) "yes")
                   yes-fn no-fn))))
            (letex (_conts conts)
              (fn () '_conts)))))))
(defnode3 'people "Is the person a man?" 'male 'female)
(defnode3 'female 'female)
(defnode3 'male "Is he living?" 'liveman 'deadman)
(defnode3 'liveman 'liveman)
(defnode3 'deadman "Was he American?" 'us 'them)
(defnode3 'them 'them)
(defnode3 'us "Is he on a coin?" 'coin 'cidence)
(defnode3 'cidence 'cidence)
(defnode3 'coin "Is the coin a penny?" 'penny 'coins)
(defnode3 'coins 'coins)
(defnode3 'penny 'lincoln)

 さて、動作は、

> (setq n (compile-net 'people))
(lambda () (print "Is the person a man?" "\n>> ") (
  (if (= (read-line) "yes") 
   (lambda () (print "Is he living?" "\n>> ") (
     (if (= (read-line) "yes") 
      (lambda () 'liveman) 
      (lambda () (print "Was he American?" "\n>> ") (
        (if (= (read-line) "yes") 
         (lambda () (print "Is he on a coin?" "\n>> ") (
           (if (= (read-line) "yes") 
            (lambda () (print "Is the coin a penny?" "\n>> ") (
              (if (= (read-line) "yes") 
               (lambda () 'lincoln) 
               (lambda () 'coins)))) 
            (lambda () 'cidence)))) 
         (lambda () 'them))))))) 
   (lambda () 'female))))
> (n)
Is the person a man?
>> yes
Is he living?
>> no
Was he American?
>> yes
Is he on a coin?
>> yes
Is the coin a penny?
>> yes
lincoln
> 

 Common Lisp と違って、戻り値のクロージャがそのまま現れる newLISP は、こういう時、便利です。*nodes3* のリスト情報が、全てクロージャ n に織り込まれているのを見ることができます。展望 に書かれている 「コンパイルする」 の意味が良くわかります。

 静的参照によるコンパイル後に、データを変えたい時、Common Lisp では、どうするのでしょうか? (例えば、lincoln を Lincolnにするとか)
 しかも、使ったリストを破棄した後だったりしたら?
 私の CommonLisp の知識では、リストの再生成、再コンパイルしかありません。しかし、newLISP では、リストの再生成も、再コンパイルも必要ありません。
  その方法は、こちらでどうぞ

 さて、第6章のまとめです。

  • newLISP でも、データ構造をクロージャで表現できます。
    しかも、そのクロージャを直接変更可能です。

 以上、如何でしょうか?

広告