Archive for the ‘do*’ Tag

newLISP で On Lisp する...第7章(その2)

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

 第7章 マクロ 、今回は、 マクロ展開の確認 から。 といっても、newLISP には、Common Lisp のように macroexpand がありません。マクロ展開の確認をするには、少々手間が要ります。まず、前回の手法を使って、マクロを組みます。例として、前回挙げたour-while を使いましょう。newLISP のマクロでは、組込letex で定義された変数が展開式に展開されます。そこで、letex に続く展開式の括弧に ‘(シングル・クォート)を付けるのです。
(do は 第3章(その1)にもありますが、下の方でも定義しています。使用には、newlisp-utility.lsp が必要です。)

(define-macro (our-while)
; (when test &body body)
  (letex (_test (args 0)
          _body (cons 'begin (1 (args))))
    '(do ()
        ((not _test))
        _body)))

 この状態で、マクロを定義して、our-while を実行すると、

> (our-while (able) (laugh))
(do () ((not (able))) 
 (begin 
  (laugh)))
> (our-while (< i 2) (print i) (i+ i))
(do () ((not (< i 2))) 
 (begin 
  (print i) 
  (+ i 1)))

 '(シングル・クォート)が付いていますから、評価されて展開式が現れます。つまり、展開されるだけで、評価されないのです。このようして、マクロの展開内容を確認できます。この展開で OK であれば、おもむろに、展開式の前の '(シングル・クォート)を外してマクロの完成です(笑)。この方法で、確認できるのは、Common Lisp の macroexpand-1 と同等レベルです。下位のマクロは、それぞれで確認する必要があります。とはいえ、これで、マクロの展開を確認できます。newLISP でマクロ組む際は、こうのようにして確認してから、実行するとデバックが楽です。
 Common Lisp では、組込マクロがたくさんあるので、macroexpand-1 を使って、組込マクロの展開を確認でき、大変参考になります。幸か不幸か、newLISP には、macroexpand がありません。どのみち newLISP の組込は、マクロでないので、意味無いですけどね。組込かどうかは、primitive? で確認できますが、lambda? では、nil になります。 関数かどうかを判定する関数functionp を

(define functionp (fn (x) (or (lambda? x) (primitive? x))))

と定義した理由です。マクロの判定には、組込macro? を使います。
 閑話休題。次の 引数リストの構造化代入 でも、Common Lisp のような destructuring-bind はありません。しかし、構造化代入は可能です。(third は、newlisp-utility.lsp に定義してあります)

(define-macro (our-dolist)
;  (our-dolist (var list &optional result) &body body)
  (letex (_var (args 0 0)
          _lst (args 0 1)
          _result (third (args 0))
          _body (cons 'begin (1 (args))))
    (begin
       (map (fn (_var) _body) _lst) 
       _result)))

 ただし、引数リストは指定せず、組込letex を使って (args) から直接全ての変数を定義するのです。
 マクロour-dolist は、Common Lisp 互換ですから、result を指定できます。

> (let (res) (our-dolist (x '(a b c) res) (push x res -1) (println x)))
a
b
c
(a b c)

 上記マクロは、“On Lisp” 本書の例に合わせた形になっています。前回の手法をさらに進めるとこうなります。

(define-macro (our-dolist)
;  (our-dolist (var list &optional result) &body body)
  (letex (_lst (args 0 1)
          _result (third (args 0))
          _func (append '(fn) (list (list (args 0 0))) (1 (args))))
    (begin
      (map _func _lst) 
      _result)))

 どちらを使うかは、お好みで。ちなみに、最後の行から、let文 を外してあるのは、本書例の let文の必要性が未だに判らないから(汗)。変数捕捉問題のような気がするのですが、問題となるような例を思いつきません。ご存知の方、教えて下さい。お願いします。また、mapc を map に変えてありますが、動作は一緒です。引数のリストが 1 つですから。
mapc は、newlisp-utility.lsp に定義してありますので、試したい方はどうぞ。
 さて、マクロwhen-bind は、

(define-macro (when-bind)
; (when-bind (var expr) &body body)
  (letex (_var  (args 0 0)
          _expr (args 0 1)
          _body (cons 'begin (1 (args))))
    (let (_var _expr)
      (when _var _body))))

 となります。動作はというと

> (let (y 'a) (when-bind (x (symbol? y)) (print y " is symblol?\n" x)))
a is symblol?
truetrue 

 もちろん、

(define-macro (when-bind)
; (when-bind (var expr) &body body)
  (letex (_var  (args 0 0)
          _expr (args 0 1)
          _body (append '(when) (list (args 0 0)) (1 (args))))
    (let (_var _expr)
      _body)))

 という具合にもできます。お好みな方で(笑)。

 マクロのモデル にある、defmacro の定義は、newLISP に destructuring-bind がないので、定義しません。組込lambda-macro を使っての定義を載せても、defun の定義(newlisp-utility.lsp にあり)と一緒ですから。また、our-setq も、トップ・レベルのレキシカル・スコープが必要なので省略。context を使って出来なくも無いですが、以前やったし。
 プログラムとしてのマクロ に進みます。
 本書の do の例は、goto を使っているの前に定義した do のマクロをベースに進めます。
 do を次のように展開すればよいと考えます。

(let ((w 3) (x 1) (y 2) (z nil))
  (until (> x 10)
      (print x)
      (print y)
      (psetq x (i+ x) y (i+ y)))
  (begin (print z) y))

 until は、newLISP組込の制御文です。princ は print に、1+ は i+ (newlisp-utility.lsp で定義)に変えてあります。
 そして、実装したのが、下記のマクロです。 (動作には、newlisp-utility.lsp が必要です。)

(context 'MAIN:do)
(defun make-stepform (bindform)
  (remove nil (mappend (fn (b) (if (and (consp b) (third b))
                               (list (car b) (third b))
                                '()))
                         bindform)))
(define-macro (do:do)
  (letex (_init (map (hayashi slice 0 2) (args 0))
          _steps (cons 'psetq (make-stepform (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)

 第3章(その1) の定義とはちょっと違いますが、こちらの方が、より、Common Lisp 互換です(当たり前か、汗)。
 マクロ展開の確認 で見たように、展開式部分( let 文のカッコの前)に '(シングル・クォート)をつけて、展開式を見てみましょう。

(let ((w 3) (x 1) (y 2) (z)) 
 (until (> x 10) 
  (begin 
   (print x) 
   (print y)) 
  (psetq x (+ x 1) y (+ y 1))) 
 (begin 
  (print z) y))

 body 部に begin がついていることを除けば、予定通りの展開です。前回の手法からすれば、body部と step部は一緒にするので begin は要りませんが、ここでは、本書と対比しやすいように分けてあるからです。また、スクリプトも書きやすいので(汗)。
 Common Lisp と同じく、マクロの展開式を作るコードは、それ自身がプログラムでもマクロでもかまいませんが、結果を letex で定義する変数に束縛させることが必要です。

 マクロのスタイル では、展開するコードの明確さが一番だとされています。newLISP のマクロは、その文法上、そうならざるを得ません。なぜなら、newLISP でマクロは、次のようなスタイルで書くことになるからです。

(define-macro (macro-name)
  (letex (vars)
     (body))

 引数から展開するコードを、全て vars に書き、vars で定義された変数を使った被展開コードを、body に書くという構成です。必要であれば、vars 用に補助コードを書けばよいわけです。もちろん、それ以外の書き方も出来ます。
 本書の例から、

(define-macro (our-and)
  (case (length (args))
    (0 t)
    (1 (args 0))
    (t (letex (if-x (args 0)
               then (cons 'our-and (1 (args))))
         (if if-x then)))))
(define-macro (our-andb)
  (if (null (args))
      t
    (define (expander rests)
      (if (cdr rests)
          (if (car rests)
              (list (append '(if) (list (car rests)) (expander (cdr rests)))))
        (list (car rests))))
    (letex (_andb (car (expander (args))))
      _andb)))

 最初のマクロour-and は、マクロ自身の再帰です。展開されるのは、letex に続く式です。例によって、その式に '(シングル・クォート)をつけて確認すると、

> (our-and a b c)
(if a 
(our-and b c))

 となります。マクロour-and で展開されたコードにマクロour-and が使われ、それがさらに展開して、、、と続くわけです。
 マクロour-andb では、マクロ内で、再帰させ展開式を作っています。

> (our-andb a b c)
(if a 
 (if b 
  c))

 補助関数をマクロ内で定義している格好です。お気に入りのマクロlabels を使わなかったのには、理由があります。Common Lisp では、マクロが呼び出された場所で展開式が評価されるので、内部関数expander が有効に働きます。しかし、newLISP では、評価はマクロ内で完結します。しかも、letex 内で引数は評価されずに展開(変形)されるので、内部関数expander に有効な引数を与えられないのです。そのため、define を使って、内部関数を定義しています。もちろん、内部関数定義にマクロdefun を使っても、大丈夫です。

 マクロへの依存 は、コンパイルが前提で書かれていますので、コンパイルの無い newLISP では、絡みようが無く、次に進みます。とはいえ、本書にある “汎用ユーティリティは、関数であれマクロであれ、プログラムの他の部分と分けておくほうがよいものである。” というのは、全くその通りだと思います。 newlisp-utility.lsp を用意した所以でもあります。

 関数からマクロへ で書かれている手法は、Common Lisp用なので、newLISP への置き換えはしません。関数で書くか、マクロで書くか。私的には、決まっています。関数でかけるものは、関数で書く。関数では書けないもの、マクロを組む。すでに書いたように、newLISP で関数とマクロの違いは、引数が評価されて渡るか、評価されずに渡るかだけです。そして、マクロには、apply や map が使えません。ですから、極力関数で書きます。それでも、defun は、関数では書けません。破壊的関数は、一部の例外を除き、関数では書けないから。do のような制御文もマクロでしか書けません。引数を評価せずに渡したいから。そんな感じです。次章以降のテーマですけどね。

 さて、最後の シンボルマクロ は、newLISP には無く、、、と、前は書きましたが、実は、あります。今までさんざん使ってきた letex がそれに相当します。

> (define symbol-macrolet letex)
letex
> (define progn begin)
begin
> [cmd]
(symbol-macrolet ((hi (progn (println "Howdy")
                               1)))
                   (+ hi 2))
[/cmd]
Howdy
3
> (letex (hi (begin (println "Howdy") 1)) (+ hi 2))
Howdy
3

 ということは、Common Lisp でも、symbol-macrolet を使って、newLISP のようにマクロを組める?試しませんけどね(笑)。

 第7章 マクロ のまとめです。

  • newLISP での関数とマクロの違いは、引数が評価されて渡るか、評価されずに渡るかだけです。
  • newLISP では組込letex で、CommonLisp のバッククォート内のカンマとカンマアットに相当させることが出来ます。

 以上、如何でしょうか?

広告

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

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

以上、如何でしょうか?

newLISP で On Lisp する。。。第1章

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
(”On newLISP” は、“On Lisp”のスクリプトを newLISP で実装してみようという、試みです。経緯は、こちらでどうぞ。)

On Lisp” は説明するまでもなく、Paul Graham氏の名著です。私はこれを読んで Lisp を始めました。私が持っているのは、野田開氏訳の日本語翻訳本(ISBN978-4-274-06637-5)です。Web上で見ることが出来ますが、願わくば、購入されますように。それだけの価値はあります。

では、“第1章 拡張可能なプログラミング言語”から。特に断らない限り、CommonLisp の動作は、xyzzy での動作です。
“Lispの拡張” にあるスクリプトは、Common Lispにとっては、特に目新しいスクリプトはありません。

; スクリプト1
(mapcar fn
        (do* ((x 1 (1+ x))
              (result (list x) (push x result)))
          ((= x 10) (nreverse result))))

しかし、newLISP にとっては、厄介なスクリプトです。do* がありませんからね。いきなり大物マクロを組まなくてはいけません。
その前に、注意点。newLISP では、fn は組込関数です。Arc のように、lambda と同じ機能です。newLISP では、mapcar は map に、nreverse は reverse に相当します。関数1+ もありませんので、関数i+ を用意します。’i+’ なのは、関数や変数に ‘1+’ という名前が使えないこと。これは仕様なのでしかたありません。
前置きはさておき、do* の実装です。xyzzy reference.chm の do* 項を参考に作りました。

; スクリプト2
(define-macro (do*)
  (letex (_init (map (hayashi slice 0 2) (args 0))
          _steps (cons 'setq (flat1 (map (hayashi select '(0 2)) (args 0))))
          _results (cons 'begin (rest (args 1)))
          _end-test (first (args 1))
          _body (cons 'begin (2 (args))))
   (letn _init
     (until _end-test
       _body
       _steps)
     _results)))

中ほどの letn は、Common Lisp では、let* に相当します。これを let に変え、newLISP 版 psetq を用意すれば、do のマクロになります。その、実装例は、こちらにありますhayashiflat1 は拙作です。newlisp-utility.lsp に定義してあります。
先ほどの、注意点を含めスクリプト1を newLISP 用に書き直すと、

; スクリプト3
(define mapcar map)
(define nreverse reverse)
(define (i+ x) (+ 1 x))
(mapcar fn
        (do* ((x 1 (i+ x))
              (result (list x) (push x result)))
          ((= x 10) (nreverse result))))

これで、動きます。
(i+ は、newlisp-utility.lspで macro定義してあります。newlisp-utility.lsp を先にロードしている場合は、i+ の定義をコメント・アウトして下さい。)
実際動作させてみると、

> [cmd]
(define mapcar map)
(define nreverse reverse)
(define (i+ x) (+ 1 x))
(mapcar (fn (x) (* 2 x))
        (do* ((x 1 (i+ x))
              (result (list x) (push x result)))
          ((= x 10) (nreverse result))))
[/cmd]
(2 4 6 8 10 12 14 16 18 20)
>

さて残り、map1-n は、第4章で実装する予定です。
for は、newLISP 標準関数なので、実装しないかも(笑)。
実のところ、数字の 1 から 10 までのリストを作るだけなら、newLISP では 組込sequence を使えば、一発で済みます。

> (sequence 1 10)
(1 2 3 4 5 6 7 8 9 10)

つまり、map1-n の実装は超簡単(笑)。
ということで、今日のまとめです。

一番の問題は、最後の項目でしょうか。私にとっては、致命的でないですけどね。
さて、newLISP が、第1章のタイトル “拡張可能なプログラミング言語” であることは、間違いのないところ。そして、章の最後の問いは、“いつ Lispか?” 。ということで、この blog の締めには、次の問いを。

いつ newLISPか?

私のお薦めは、こうです。

newLISP V.10.2.8 から

以上、如何でしょうか?