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はうってつけ?
push、pop のように副作用がメインの関数もありますけどね。
ちなみに、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 の相当します。
さて、長くなってきたので、命令型プログラミングの裏返し からは、次回に。
以上、如何でしょうか?