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

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

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
今回は、第3章 関数プログラミング です。
まずは、関数型デザイン から。
最初から難問です。実は、関数として、bad-reverse が定義できないのです。実装という意味では、do も用意しましたし、この場合のrotatef に代わる 組込関数swap があります。let* もそれに代わる letn があります。記述は出来るのです。でも、動作しません。実は、newLISP では、関数に引数が渡される時点で、引数のコピーがとられ、コピーされた引数が関数に渡されます。つまり、引数を破壊する関数は書けない。どうです、まさに関数プログラミング向けといえるのでは。
前置きが長くなりましたが、見てみましょう。
(psetq、hayashi、defun、i+、i- は、newlisp-utility.lsp に定義してあります)

(context 'do)
(define-macro (do:do)
  (letex (_init (map (hayashi slice 0 2) (args 0))
          _steps (cons 'psetq (flat1 (map (hayashi select '(0 2)) (args 0))))
          _results (cons 'begin (rest (args 1)))
          _end-test (first (args 1))
          _body (cons 'begin (2 (args))))
   (let _init
     (until _end-test
       _body
       _steps)
     _results)))
(context MAIN)
(defun bad-reverse (lst)
  (letn ((len (length lst))
         (ilimit (/ len 2)))
    (do ((i 0 (i+ i))
         (j (i- len) (i- j)))
        ((>= i ilimit))
      (swap (nth i lst) (nth j lst)))))

動作はというと、

> (bad-reverse lst)
nil
> lst
(a b c)

戻り値は逆転していますが、元々の lst は変わっていません。
と言っても、関数でかけないのであって、マクロでなら書けます。

(define-macro (badm-reverse)
  (letex (_lst (args 0))
    (letn ((len (length _lst))
           (ilimit (/ len 2)))
      (do ((i 0 (i+ i))
           (j (i- len) (i- j)))
          ((>= i ilimit))
        (swap (nth i _lst) (nth j _lst))))))

letn 以降は、変数_lst を使っている以外、関数定義と一緒です。
動作は、

> (badm-reverse lst)
nil
> lst
(c b a)

この通り、lst の中身が変わっています。
さて、good-reverse の方に。こちらは、labels が定義されているので簡単です。
(labels、car、cdr、null は、newlisp-utility.lsp に定義してあります)

(defun good-reverse (lst)
  (labels ((rev (lst acc)
             (if (null lst)
                 acc
               (rev (cdr lst) (cons (car lst) acc)))))
    (rev lst '())))

動作にも、問題はありません。

> (setq lst '(a b c))
(a b c)
> (good-reverse lst)
(c b a)
> lst
(a b c)

より良い関数型プログラミングを目指すためには、newLISPはうってつけ?
pushpop のように副作用がメインの関数もありますけどね。
ちなみに、newLISP の組込reverse は、戻り値と同じように元のリストを破壊します。

> (setq lst '(a b c))
(a b c)
> (reverse lst)
(c b a)
> lst
(c b a)
> (reverse (copy lst))
(a b c)
> lst
(c b a)

破壊したくない時は、引数を copy して渡します。他の破壊的関数も同様です。
さて、Common Lisp の nconc は、newLISP では extend に相当します。
しかし、第二引数以降まで副作用を起こす意味では、マクロが必要です。

(context 'nconc)
(define-macro (nconc:nconc)
  (letex (_lst0 (args 0)
          _det1 (> (length (args)) 1)
          _rest (cons 'nconc (1 (args))))
    (if-not _det1 _lst0
       (extend _lst0 _rest))))
(context MAIN)

extend と nconc との違いは、

> (setq x '(1) y '(2) z '(3))
(3)
> (extend x y z)
(1 2 3)
> (list x y z)
((1 2 3) (2) (3))
> (setq x '(1) y '(2) z '(3))
(3)
> (nconc x y z)
(1 2 3)
> (setq x '(1) y '(2) z '(3))
(3)
> (nconc x y z)
(1 2 3)
> (list x y z)
((1 2 3) (2 3) (3))

となります。
さて、Common Lisp で、(nconc x y)(setq x (nconc x y) の違いは、

[1]> (setq x nil y '(y))
(Y)
[2]> (nconc x y)
(Y)
[3]> x
NIL
[4]> (setq x '())
NIL
[5]> (nconc x y)
(Y)
[6]> x
NIL
[7]> (setq x (nconc x y)
(Y)
[8]> x
(Y)

こういう場合のようです。
newLISP では、

> (setq x nil y '(y))
(y)
> (nconc x y)

ERR: list or string expected in function extend : nil
> (setq x '())
()
> (nconc x y)
(y)
> x
(y)

となります。マクロnconc の代わりに組込extend を使っても一緒です。
newLISP の場合、組込の破壊的関数は、ほとんどの場合で副作用のために呼ばれます。
これも、Common Lisp との違いの一つかもしれません(笑)。
とは言え、副作用の使用を勧めている訳ではありません。
“On Lisp” に書いてあるように、ある程度の副作用は不可避です。
前述のように newLISP の関数定義では副作用を持てない分、組込関数で補っていると見るべきでしょう。
さて、CommonLisp にある多値の戻り値は、newLISP にはありません。だからといって、関数型プログラミングの妨げにはなりません。リストで返せばよいだけですから。
Common Lisp の多値を使った truncate と multiplue-value-bind の newLISP での実装は、前回、既に済ましましたので、関数powers の例だけ示します。

> [cmd]
(defun powers (x)
  (values x (sqrt x) (pow x 2)))
(multiple-value-bind (base root square) (powers 4)
  (list base root square))
[/cmd]
(lambda (x) (values x (sqrt x) (pow x 2)))
(4 2 16)

Common Lisp の exprt は、newLISP の組込pow の相当します。

さて、長くなってきたので、命令型プログラミングの裏返し からは、次回に。

以上、如何でしょうか?