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

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

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

 今回から、第5章 返り値としての関数
 まず、Common Lisp は進化する では、スクリプト例がいくつかありますが、“On Lisp”本書の主張は、

“レキシカル・スコープなら、それだけで、実行時にクロージャを生成できる。”

ということです。
決して、

“レキシカルスコープと違って、ダイナミック・スコープでは実行時にクロージャを生成できないし、出来たとしても、内部変数が、最終的に呼び出された環境に左右される”

 ではありません。なぜなら、Common Lisp からすればダイナミック・スコープといえる newLISP でも、実行時にクロージャを生成可能だからです。それは、第2章(その3)で make-adder の例 を実証しました。また、そこでは、内部状態を変化させられるクロージャも可能であることも示しました。
 ということは、newLISP は、レキシカル・スコープ?そんな訳はありません。こう言い換えるべきでしょう、

“ダイナミック・スコープでも、レキシカル・スコープのようなクロージャを生成できる言語もある。newLISP のように。”

 冗談(笑)は、さておき、スクリプトを実装しましょう。(defun は newlisp-utility.lsp に定義してあります)

(defun joiner (obj)
  (case (type-of obj)
    ("list" append)
    ("integer" +)
    ("float" add)))
(defun joinEx ()
  (apply (joiner (args 0)) (args)))

 newLISP には、組込join があるので、joinEx に改名してあります。また、newLISP には、typecase は無いので、前に紹介したこと のある ArtfulCode ユーティリティ の type-of と組込<a href="http://www.newlisp.org/downloads/newlisp_manual.html#case"case を組み合わせています。
 動作は、

> (joinEx 1 2 3)
6
> (joinEx 1.1 2.2 3.3)
6.6
> (joinEx '(1) '(2 3) '(4 5 6))
(1 2 3 4 5 6)

 ダイナミックスコープの下でできることだから、当たり前。
 make-adder は、第2章(その3)で試しているので、次の関数complement を

(defun complement (f)
  (letex (func f)
    (fn () (not (apply func (args))))))

 もちろん、所望の動作をします(remove-if、evenp、oddp、numberp は newlisp-utility.lsp に定義してあります)。

> evenp
(lambda (num) (= (& num 1) 0))
> oddp
(lambda (num) (= (& num 1) 1))
> remove-if
clean
> (remove-if oddp '(1 2 3 4 5 6))
(2 4 6)
> (remove-if (complement oddp) '(1 2 3 4 5 6))
(1 3 5)
> numberp
number?
> (remove-if (complement numberp) '(1 a 2 b 3 c))
(1 2 3)
> (setq e? (complement oddp))
(lambda () (not (apply (lambda (num) (= (& num 1) 1)) (args))))
> (map e? '(1 2))
(nil true)
> (setq s? (complement numberp))
(lambda () (not (apply number? (args))))
> (map s? '(a 1))
(true nil)

 引数の関数が、組込であれ、自作であれ、クロージャ内に束縛されているのを見ることができます。つまり、Common Lisp 同様、newLISP も抽象化のための強力な道具を手にしています。

 次は、直交性 。newLISP も、直交的なプログラミング言語だといえると思います。最初は、等価で破壊的な関数を返すオペレータを newLISP で定義した例です。newLISP には、ハッシュがありませんので、代わりに context のハッシュ的な使い方を使っています。

(define *equivs*:*equivs*)
(defun !! (f)
  (or  (*equivs* (string "'" f)) f))
(defun def! (f f!)
  (*equivs* (string "'" f) '())
  (setf (*equivs* (string "'" f)) f!))

 ! 単品は、newLISP では、組込関数なので、!! に名前を変えてあります。関数def! の一行目は、context 内に、シンボルを用意しておかないと、setf が変更できる対象がないというエラーになるからです。
 動作は、破壊的関数delete-if がないので、用意します。

(define-macro (delete-if)
  (let (_sym (symbol? (args 1)))
    (letex (_fn (args 0)
            _lst (args 1))
      (if _sym
          (setf _lst (clean _fn _lst))
        (clean _fn _lst)))))

そして、

> (def! remove-if delete-if)
(lambda-macro () 
 (let (_sym (symbol? (args 1))) 
  (letex (_fn (args 0) _lst (args 1)) 
   (if _sym 
    (setf _lst (clean _fn _lst)) 
    (clean _fn _lst)))))
> (setq lst (sequence 0 9))
(0 1 2 3 4 5 6 7 8 9)
> (remove-if evenp lst)
(1 3 5 7 9)
> lst
(0 1 2 3 4 5 6 7 8 9)
> ((!! remove-if) evenp lst)
(1 3 5 7 9)
> lst
(1 3 5 7 9)
> ((!! remove-if) evenp (sequence 0 9))
(1 3 5 7 9)
> (remove-if evenp (sequence 0 9))
(1 3 5 7 9)

 所望の動作をしています。newLISP では、破壊と非破壊の両方の関数がある場合が少ないので、そういう用途で使うことは、あまりないでしょうけど(笑)。

 関数の値のメモ化 の、関数memoize は、newLISP の解説書 “Code Patterns in newLISP” にあります(“Speed up with memoization” の項)。ただし、紹介されているコードは、関数を定義するマクロです。本章に合わせてクロージャを返す関数に再定義します。
(gensym は newlisp-utility.lsp に定義してあります)

(defun memoize (func) 
  (let (ctx (gensym))
    (context ctx)
    (letex (f func  c ctx) 
      (fn () 
        (or (context c (string (args))) 
            (context c (string (args)) (apply f (args))))))))

もちろん、context を使っています。そして、動作例です。

> (setq slowid (memoize (fn (x) (sleep 5000) x)))
(lambda () (or (context gensym3 (string (args))) (context gensym3 (string (args)) 
   (apply (lambda (x) (sleep 5000) x) (args)))))
> (time (slowid 1))
5000
> (time (slowid 1))
0

newLISP の場合、組込sleep の引数と組込time の戻り値の単位は、共に、ミリ秒です。

切りが良いので、関数を合成する は、次回に。

以上、如何でしょうか? 

広告