Archive for the ‘reverse’ Tag

newLISP の reverse を使う。

 newLISP の組込reverse は、破壊的です。マニュアルによれば、

newLISP の組込の多くは、非破壊的( 副作用 無し)であり、既存のオブジェクトをそのまま残し、新しいオブジェクトを作ります。しかしながら、数少ない関数が、変数の内容やリスト、アレイ、文字列を変更します
Most of the primitives in newLISP are nondestructive (no side effects) and leave existing objects untouched, although they may create new ones. There are a few destructive functions, however, that do change the contents of a variable, list, array, or string.

 従って、Common Lisp のように reverse を使うと、副作用が問題になることがあります。
 しかし、回避方法もあります。同じくマニュアルから、

いくつかの破壊的関数は、目標オブジェクトを関数 copy で包むことによって非破壊的関数にできます。
Some destructive functions can be made non-destructive by wrapping the target object into the copy function.

(set 'aList '(a b c d e f))
(replace 'c (copy aList))  (a b d e f)
aList  (a b c d e f)

aList のリストは、変更されずに残ります。
The list in aList is left unchanged.

 と、いった具合です。
 さて、入れ子のリスト式 の中の部分リストを reverse したい場合、

> (setq lsts '((1 2 3)(4 5 6)(7 8 9)))
((1 2 3) (4 5 6) (7 8 9))
> (setf (lsts 0) (reverse (lsts 0)))
(3 2 1)
> lsts
((3 2 1) (4 5 6) (7 8 9))
> 

 こんな感じ。えっ、$it を使わないのかって?
 それは、使ってみれば判ります。

> (setf (lsts 0) (reverse $it))

ERR: symbol is protected in function reverse : $it
> 

 $it は、保護されているシステム変数です。reverse のような破壊的関数を適用すると、エラーします。
 そのため、ここでも組込copy が必要です。

> (setf (lsts 0) (reverse (copy $it)))
(1 2 3)
> lsts
((1 2 3) (4 5 6) (7 8 9))
> 

 このように、入れ子リストの部分リストを変更するには、setf暗黙のインデックス機能が使えます。
 そして、部分リストを全部 reverse するには map で十分ですが、

> (map reverse lsts)
((3 2 1) (6 5 4) (9 8 7))
> lsts
((1 2 3) (4 5 6) (7 8 9))
> 

 lsts 自体は変更されません。これは、組込map が非破壊なので lsts のコピーが引数として使われるためでしょう。
lsts 自体を変更するには、

> (setq lsts (map reverse lsts))
((3 2 1) (6 5 4) (9 8 7))
> lsts
((3 2 1) (6 5 4) (9 8 7))
> 

 と、するしかないようです。まっ、これで十分ですけどね(笑)。
 こんな reverse の使い方をする理由は? それはまた、別の機会に。

 以上、如何でしょうか?

広告

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 の相当します。

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

以上、如何でしょうか?