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

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

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

 第14章 アナフォリックマクロ です。
 V.10.0以降の newLISP では、setf などで使えるアナフォリックなシステム変数 $it が導入されています。

 まずは、Common Lisp の標準オペレータのアナフォリックな変種 から、
(third は、newlisp-utility.lsp で定義してあります。)

(define-macro (aif)
; (aif test-form then-form &optional else-form)
  (letex (_test-form (args 0)
          _then-form (args 1)
          _else-form (third (args)))
    (let (it _test-form)
      (if it _then-form _else-form))))

 上記マクロ aif は、“On Lisp”本書に沿ったマクロです。
 しかし、newlisp-utility.lsp には、macro の aif が定義されています。

(module "macro.lsp")
(macro (aif S A B)
  (let (it S) 
    (if it A B))))

 どちらを使っても、結果は同じですが、どちらか一方しか使えません。ご注意を。
 お薦めは、macro の aif です(笑)。
 さて、このマクロを使えば、組込find で find-if がすっきり書けます(defun は、newlisp-utility.lsp に定義してあります)。

(defun find-if (func lst)
  (aif (find nil lst (fn (x y) (func y)))
       (lst it)))

 動作は(oddp は、newlisp-utility.lsp に定義してあります)、

> (find-if oddp '(2 3 4))
3
> 

 もちろん、次のマクロ

(define-macro (awhen)
; (awhen test-form &body body)
  (letex (_test-form (args 0)
          _body (cons 'begin (1 (args))))
    (aif _test-form _body)))

 を使って

(defun find-if (func lst)
  (awhen (find nil lst (fn (x y) (func y)))
       (lst it)))

 でもかまいません。
 マクロawhile には、do ではなくnewLISP の組込while を使います。

(define-macro (awhile)
; (awhile expr &body body)
  (letex (_expr (args 0)
          _body (cons 'begin (1 (args))))
    (while (setq it _expr)
      _body)))

 使い方はというと、

> (let (x '(apple orange melon)) (awhile (pop x) (print "eat ") (println it) ) x)
eat apple
eat orange
eat melon
()

 そして、マクロaand は、

(define-macro (aand)
; (aand  &rest args)
  (if (args)
      (letex (_len  (length (args))
              _1st  (args 0)
              _rest (cons 'aand (1 (args))))
        (cond ;((zero? _len) nil) 
              ((= _len 1) _1st)
              (true (aif _1st _rest))))
    true)
)

 前の“On newLISP”の頃、newLISP組込の and は、引数無しで nil を返しましたが、現在では、Common Lisp と同じ、真(true)を返します。
 さて、動作は( lookup は、newLISPでは、組込です)、

> [cmd]
(setq person '((marry ((Address ((City NewYork) (Street "123 Main Street")))(Job "Engineer")))
               (john  ((Address ((City Chicago) (Street "124 Main Street")))(Job "Desinger")))))
[/cmd]
((marry ((Address ((City NewYork) (Street "123 Main Street"))) (Job "Engineer"))) 
 (john ((Address ((City Chicago) (Street "124 Main Street"))) (Job "Desinger"))))
> (aand (lookup 'marry person) (lookup 'Address it) (lookup 'City it))
NewYork
> (aand (lookup 'marry person) (lookup 'Address it) (lookup 'Street it))
"123 Main Street"
> (aand (lookup 'john person) (lookup 'Address it))
((City Chicago) (Street "124 Main Street"))
> (aand (lookup 'john person) (lookup 'Job it))
"Desinger"
> 

 そして、マクロacond は、再帰的にマクロを呼び出しています。

(define-macro (acond)
; (acond &rest clauses)
  (if (null? (args))
      nil
    (letex (_cl1st  (args 0 0)
            _clrest (cons 'begin (1 (args 0)))
            _acondrest (cons 'acond (1 (args))) 
            _sym (gensym)) 
      (let (_sym _cl1st)
        (if _sym
            (let (it _sym) _clrest)
          _acondrest)))))

 さらなるアナフォリックなオペレータ からは、(labels は、newlisp-utility.lsp に定義してあります)

(define-macro (alambda)
; (alambda parms &body body)
  (letex (_funcbody (append '(_self) (list (args 0)) (1 (args))))
    (labels (_funcbody)
      _self)))

 現在の newLISP では、self は 組込関数なので、変数_self とアンダースコアを付けてあります。
 それを除けは、マクロlabels があるので、実装は簡単です(i- は、newlisp-utility.lsp に定義してあります) 。

> ((alambda (x) (if (= x 0) 1 (* x (_self (i- x))))) 3)
6
> 

 “On Lisp”本書の例count-instances を定義すると、

(defun count-instances (obj lists)
  (map (alambda (lst)
         (if lst
             (+ (if (= (first lst) obj) 1 0)
                (_self (rest lst)))
           0))
         lists))

 そして、動作は、

> (count-instances 'a '((a b c) (d a r p a) (d a r) (a a)))
(1 2 1 2)

 さて、newLISPには、block も return もないので、ablock の代わりに、abegin を定義します(笑)。

(define-macro (abegin)
; (abegin &rest args)
  (letex (_args (args))
    ((alambda (exprs)
      (case (length exprs)
        (0 nil)
        (1 (eval (first exprs)))
        (t (let (it (eval (first exprs)))
             (_self (rest exprs))))))
     '_args)))

 ポイントは、最後の行のクォート(’)。
 いかにも命令型の例(笑)を、

> [cmd]
(abegin 
    (println "ho ")
    (println (string it 1 ))
    (println (string it 2 )) 
    (println (string it 3 ))
    nil)
[/cmd]
ho 
ho 1
ho 12
ho 123
nil
> 

 失敗 からは、次回のお楽しみ(笑)。

 以上、如何でしょうか?