Archive for 2010年6月|Monthly archive page

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

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

 第4章 ユーティリティ関数 も、その5まで来ましたが、今回でこの章は、終了です。
 最初に、入出力入出力関数 を newLISP で書くとこうなります。

(defun readlist ((in 0))
  (read-expr (string "(" (read-line in) ")")))

(defun prompt ()
  (print (apply format (args)))
  (read-expr (read-line)))

(defun break-loop (f quit)
  (print (format "Entering break-loop.\n"))
  (catch 
    (while true
      (let ((in (apply prompt (args))))
        (if (quit in)
            (throw in)
          (print (format "%s\n" (string (f in)))))))))

 まずは、関数readlist から。newLISP には、read-from-string はありませんが、組込関数read-expr がそれに相当します。read-from-stringのような第2値を返すことは無いので、values は使う必要はありません。文字列を生成している concatenate ‘string にとって代わるのは、組込関数string です。そして、組込関数read-line は、Common Listの同関数を同等の機能ですが、引数オプションは、device番号だけです。device番号 0 で、標準入出力になります。
 動作はこうです。

> (readlist)
Call me "Ed"
(Call me "Ed")

 関数prompt は、標準入出力に特化して記述しています。
使われている format は、newLISP にもありますが、出力機能はありません。
それで、組込関数print で出力させます。
そして、read は newLISP にもありますが、Common Lisp の read というよりは、C言語の read に近い関数です。そのため、関数readlist と同様、組込関数read-lineread-expr で代用します。
 動作例です。

> (prompt "Enter a number between %d and %d.\n>> " 1 10)
Enter a number between 1 and 10.
>> 3
3

 format の書式は、これも C言語の printf の書式とほぼ同等です。C言語に慣れている私には、うれしい仕様です。
そして、入出力 の最後は、関数break-loop 。newLISP には、loop がありませんので、組込while + true で無限ループとし、同様に newLISP には無い return は、組込catchthrow で対応しています。
 では、動作を。

> (break-loop eval (fn (x) (= x 'quit)) ">> ")
Entering break-loop.
>> (+ 2 3)
5
>> quit
quit

 終了条件のシンボルに :q を使っていないのは、訳があります。 は、newLISP では、コンテキストとシンボルの区切りに使われる演算子です。つまり、:q というシンボルは、使えないということ。

 次は、シンボルと文字列 です。シンボルと文字列に作用する関数 は、以下のようになります。

(define mkstr string)

(defun symb ()
  (sym (apply mkstr (args))))

(defun reread()
  (read-expr (apply mkstr (args))))

(defun explode-s (s)
  (map sym (explode (string s))))

 まずは、関数mkstr。newLISP には、with-output-to-string がありません。それでも、本文風に書くなら、

(defun mkstr ()
  (let (res "")
    (dolist (a (args))
       (push (string a) res -1))
  res))

 という具合です。もともと、newLISP では、文字列変換関数は、特定の数値文字列変換関数以外には、string しかないのです。そして、組込関数string の機能は、まさに、関数mkstr そのものなのです。動作はというと。

> (define pi (mul 2 (acos 0)))
3.141592654
> (mkstr pi " pieces of " 'pi)
"3.141592654 pieces of pi"

 newLISP には、円周率を返す pi が無いので、組込の逆余弦関数acosを使って定義しています。
次の関数symb で使われている、intern に相当するのが、組込関数sym 。values と #’ はありませんが、本文そのままといったところ。動作はというと、

> (symb 'ar "Madi" "L" "L" 0)
arMadiLL0
> (symbol? (symb 'ar "Madi" "L" "L" 0))
true

 || で囲まれたりしませんけどね。ちなみに、xyzzy でも || は、付きません。clisp は、もちろん付きますよ。
また、newLISP には、データ型に、文字列はありますが、文字はありません。組込関数char で引数に数字を与えて返るのも一文字の文字列です。
 そして、関数reread です。やっていることは、関数prompt で標準入力から取り込んでいたオブジェクトを引数で与えているだけです。 動作的には、これでよいはずですが。

> (reread (+ 2 3))
5
> (reread 'a)
a

 xyzzy でも、Common Lispでも同じ動作でした。もっとも、Common Lispでは、a は A になりますけど。
シンボルと文字列 の最後は、関数explode です。newLISPには、組込explode がありますので、explode-s と名前を変えてあります。組込explode が文字列を一文字ずつに分けたリストを返してくれますので、本文の様に手の込んだことをしなくて済みます(笑)。
 動作も、

> (explode-s 'bomb)
(b o m b)
> (map symbol? (explode-s 'bomb))
(true true true true)

といった具合です。

 この章の最後、密度 に書かれている内容は、別に Lisp に限ったことではありません。“ボトムアップ式のプログラムは概念の密度が高い” というのは、良いユーティリティ(または、ライブラリ)があると、概念の密度が高いプログラムが書けるということでもあります。ニワトリが先か、タマゴが先か。ちにみに、“概念の密度が高いプログラム” は “アルゴリズムが見通せるプログラム” だと思いますが、如何でしょうか?

 さて、第4章 ユーティリティ関数 のまとめです。

  • newLISP の組込関数last は、CommonLispの last とは違い、last1 と同等です。
  • newLISP には nthcdr と subseq はないが、どちらも、インデクシングで実現できます。
  • newLISP では、eq、eql、equal の区別が無く。全て、= で対応します。
  • newLISP の組込関数member は、CommonLisp と違って、判定条件の指定ができません。
  • newLISP 演算子 +、-、*、/、% は、整数専用です。しかし、実数用に、再定義する方法が用意されています。
  • newLISP には、制御文loop はありません。その代わり、while、until、for 等が使えます。
  • newLISP では、apply と map の組合せ使用方法が、Common Lisp と異なります。
  • newLISP の組込関数format には、出力機能はありません。また、その書式は、C言語の printf の書式似です。
  • newLISP のデータ型に、文字(charcter)は、ありません。全て文字列で対応します。

以上、如何でしょうか?

newLISP マニュアル・アップデート v.10.2.8 rev-4

newLISP マニュアルrev-4 にアップデートされました。

と言っても、HTTP-only server mode の章の最後に環境変数に関する一文が追加されただけです。

つまり、関数リファレンスの変更はないので、“newLISP 関数リファレンス・スクリプト” と “日本語併記関数リファレンス” は、アップデートしていません。

あしからず((笑)。

以上、如何でしょうか?

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

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

第4章 ユーティリティ関数 もいよいよ マッピング です。map系関数のうち、map0-n から map-> までは、こうなります。

(defun map0-n (f n) (mapa-b f 0 n))
(defun map1-n (f n) (mapa-b f 1 n))
(defun mapa-b (f a b (step 1))
  (let (result)
    (for (i a b step)
      (push (f i) result -1))
  result))
(defun map-> (f start test-fn succ-fn)
  (let ((i start) (result))
    (until (test-fn i)
      (push (f i) result -1)
      (setq i (succ-fn i)))
  result))

まずは、関数mapa-b から。本文では、制御文do を使っています。以前定義した do を使っても良いのですが、do 文を使ってやっていることは、newLISP組込for に相当するので、それを使っています。もう、何度も書いていますが、newLISP組込push は、挿入位置を前方ではなく後方に出来ます。push の引数の最後の -1 が、その指定です。
では、動作を

> (mapa-b (curry add 1) -2 0 .5)
(-1 -0.5 0 0.5 1)

という具合です。組込関数curry は、以前から紹介しているように newLISP とっておきの関数です(楽)。1 を足すのに + ではなく、組込関数add を使っているのは、+ が整数専用だからです。試しに + を使うと

> (mapa-b (curry + 1) -2 0 .5)
(-1 0 0 1 1)

となります。この仕様がいやだという人は、組込constant を使って、

> (constant (global '+) add)
add
> (mapa-b (curry + 1) -2 0 .5)
(-1 -0.5 0 0.5 1)

とすることもできます。整数専用の演算子 +、-、*、/、% の実数用はそれぞれaddsubmuldivmod ですから、予め

(constant (global '+) add))
(constant (global '-) sub))
(constant (global '*) mul))
(constant (global '/) div))
(constant (global '%) mod))

と設定しておけば、整数・実数を問わず使えるようになります。お好みでどうぞ。数値計算ライブラリの先頭あたりにおいておくと良いかもしれません。
ちなみに、組込for の引数は、最初から、実数が使えたりします(笑)。
話を関数mapa-b に戻します。前に紹介した do を使った場合はこうなります。

(defun mapa-b (f a b (step 1))
  (do ((i a (add i step))
       (result '() result))
      ((> i b) (reverse result))
    (push (f i) result)))

mapa-b さえ出来れば、関数map0-n や map1-n は、簡単!
その前に、newLISPでは、関数mapa-b の動作を、組込関数sequencemap の組み合わせでも実現できます。

> (map (curry add 1) (sequence -2 0 .5))
(-1 -0.5 0 0.5 1)

ですから、mapa-b 関連は、こうも書けます。

(defun map0-n (f n)
  (map f (sequence 0 n 1)))
(defun map1-n (f n)
  (map f (sequence 1 n 1)))
(defun mapa-b (f a b (step 1))
  (map f (sequence a b step)))

もちろん、 map0-n と map1-n に mapa-b を使ってもいいですが、書く手間はあまり変わりません。ならば、わざわざ関数を呼び出すオーバーヘッドを増やす必要はありません。ユーティリティですから(笑)。
多機能の Common Lisp にも、たぶん sequence 相当する関数があるのかもしれまんが、約1千個もある関数から見つけ出すのは容易ではありません。
newLISPなら、すぐ見つかりますけどね(笑)。
newLISP の宣伝はさておき、次は、関数map-> です。
こちらも、do を使わず、newLISP組込の制御文until を使っています。
do を使った場合は、こうなります。

(defun map-> (f start test-fn succ-fn)
  (do ((i start (succ-fn i))
       (result nil result))
      ((test-fn i) (reverse result))
    (push (f i) result)))

そして、関数map-> を使って、関数mapa-b を定義すると、こうなります。

(defun mapa-b (f a b (step 1))
  (map-> f
         a
         (fn (x) (> x b))
         (fn (x) (add x step))))

λ式に、<a href="http://www.newlisp.org/downloads/newlisp_manual.html#fn"fn が使えると楽です。
次は、関数mappend の定義。

(defun mappend ()
  (apply append (apply map (args))))

第4章(その1)で定義したmapcanextend(nconc の代用)を append に変えただけです。
元はといえば、“On Lisp” 本文にある関数our-mapcan の定義を newLISP風にアレンジしたもの。
それはさておき、第4章(その1)で定義した関数nicknames で試してみましょう。

> (mappend nicknames '("a 1" "b 2" "c 3") '())
("1" "2" "3")

さて、関数mapcars は、

(defun mapcars (f)
  (let (lsts (args))
    (let (result)
      (dolist (lst lsts)
        (dolist (obj lst)
          (push (f obj) result -1)))
      result)))

内部変数lsts に使っている args の引数指定は要りません。newLISP組込ですから。上記のように、わざわざ let文 を使って、lsts に束縛する必要もありません。let文をはずして、lsts を (args) に置き換えても動きます。動作はというと、

> (mapcars sqrt '(1 2) '(3 4))
(1 1.414213562 1.732050808 2)

こんなところでしょうか?
マッピング の最後は、関数rmapcar ですが、ここで使っている、関数some は、newLISP にありませんので、先に定義します。

(define-macro (some)
  (letex (_exec (append '(map) (args)))
     (apply or _exec)))

(define-macro (every)
  (letex (_exec (append '(map) (args)))
     (apply and _exec)))

some と every は対でしょうから、ついでに定義してみました。これで、関数rmapcar を定義できます。しかし、この関数での some の使い方では、newLISP組込関数exists で、十分です。

(defun rmapcar (fx)
  (let (lsts (args))
    (labels ((rmaps (f lst)
             (let (res)
               (dolist (x lst)
                 (if (exists atom x)
                     (push (apply f x) res -1)
                   (push (rmaps f (transpose x)) res -1)))
               res)))
      (if (exists atom lsts)
          (apply fx lsts)
        (rmaps fx (transpose lsts))))))

“On Lisp”本文より複雑見えますが、labels を使って、動作を分けたのが味噌。もちろん、labels は、newLISP にはありません。こちらで定義した拙作マクロです。この場合、let を使った内部変数lsts の束縛は必須です。labels内で定義した関数の args と競合しますから(汗)。
動作はというと、

> (rmapcar sqrt '(1 2 (3 4 (5) 6) 7 (8 9)))
(1 1.414213562 (1.732050808 2 (2.236067977) 2.449489743) 2.645751311 (2.828427125 
  3))
> (rmapcar print '(1 2 (3 4 (5) 6) 7 (8 9)))
123456789(1 2 (3 4 (5) 6) 7 (8 9))
> (rmapcar + '(1 (2 (3) 4)) '(10 (20 (30) 40)))
(11 (22 (33) 44))

本文の関数rmapcar と同じように機能します。
また、exists を 先に定義した some に置き換えても動作は同じです。実は、組込exists は、引数のリストを一つしか取れません。それで、some の置き換えにならないのです。上記rmapcar の例では、引数を一つのリストにしていますから、組込exists で十分機能します。ちなみに、every には、組込関数for-all が相当します。引数のリストを一つしか取れないのは、exists と同様です。
この rmapcar は、改良されてはいますが、前回の “On newLISP” とほぼ同じアルゴリズム。
今回は、さらに “On Lisp” と同じアルゴリズムに挑戦します(笑)。

(context 'MAIN:curryEx)
(define-macro (curryEx:curryEx)
  (letex (_func (args 0)
          _arg  (args 1))
    (fn () (apply _func (cons _arg $args)))))
(context MAIN)
(defun rmapcar (f)
  (let (lsts (args))
    (if (exists atom lsts)
        (apply f lsts)
      (apply (curryEx map 
                      (fn () 
                        (apply (curryEx rmapcar f) $args)))
	           lsts))))

マクロcurryEx を定義することで、ほぼ“On Lisp”と同じ記述に持ち込めました(笑)。
動作は、

> (rmapcar + '(1 (2 (3) 4)) '(10 (20 (30) 40)) '(100 (200 (300) 400)))
(111 (222 (333) 444))
> (rmapcar (curry + 1) '(1 (2 (3) 4)))
(2 (3 (4) 5))
> (rmapcar + 1 2 3 4)
10

何故、これで動くのか?
newLISP組込curry で作成した関数は、引数を一個しかもちません。
前回検討時、上記関数のcurryEx を curry にした形で試していました。
結果は、引数一個の例(sqrt)は動くのに、引数が二個の例(+)はうまく動作しなかったのです。それで、組込transpose を使って引数リストを置換し、引数を対応する括弧ごとのリストを作っていたのです。
しかし、今回作成したマクロcurryEx が作る関数は、複数の引数を持つことができます。

> (curry + 1)
(lambda () (+ 1 ($args 0)))
> (curryEx + 1)
(lambda () (apply + (cons 1 $args)))

これで、“On Lisp”のアルゴリズムをそのまま使えるようになりました。
何故、前回の “On newLISP” の時、気が付かなかったんだか(嘆)、、、

さて、入出力 からの残りは、次回に。

以上、如何でしょうか?

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

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

さて、第4章 の 検索 です。
リストを検索する関数 は、こうなります。

(defun before (x y lst (test =))
  (and lst
       (let ((first-a (car lst)))
         (cond ((test y first-a) nil)
               ((test x first-a) lst)
               (t (before x y (cdr lst) test))))))

(defun member+ (key lst (test =))
  (let (i (find key lst test))
    (and i (i lst))))

(defun after (x y lst (test =))
  (let (rest-a (before y x lst test))
    (and rest-a (member+ x rest-a test))))

(defun duplicate (obj lst (test =))
  (member+ obj (cdr (member+ obj lst test)) test))

(defun split-if (f lst)
  (let (acc)
    (do ((src lst (cdr src)))
      ((or (null src) (f (car src)))
       (values acc src))
      (push (car src) acc -1))))

関数find2 は、第4章(その1)に掲載していますので、割愛します。本文のスクリプトからfuncall を外しただけですし。もちろん、それだけで済んでいるのは、newlisp-utility.lsp の定義を使っているからです。また、values の定義は、こちらにあります。
関数before も、funcall を外してあります。また、firstrest は、newLISP の組込関数なので、それぞれ、first-a と rest-a に変えてあります。
そして、&key オプション。CommonLisp の &key オプションは、便利ですよね。引数の位置によらず、特定の引数を指定できますから。残念ながら、newLISP に、この引数オプションはありません。だからといって、引数の位置によらない特定引数の束縛ができないかというと、これができます。もちろん、内部変数としてですよ。以前、解説した内容がこちらにあります
ここでは &key オプションが一つだけなので、newLISP お得意の暗黙の引数という形(つまり、引数をつけなくても、文句を言われない)で対応します。デフォルトの 判定条件は、= です。newLISP では、eq、eql、equal の区別がありません。全て、= で対応します。
では、動作を。

> (before 'a 'b '(a b c d))
(a b c d)
> (before 'a 'b '(a))
(a)
> (before 'b 'a '(a b c d))
nil
> (before 'A 'B '(a b A B))
(A B)

また、本文にある素の Lisp による表現の関数position はありませんが、組込関数find で代用できます。

> (< (find 'a '(a b c d)) (find 'b '(a b c d)))
true

といった感じです。
次の、関数before で使われている関数member は、newLISPにもありますが、判定条件の指定ができません。そこで、CommonLisp互換っぽい関数member+ を用意して、使っています。
後の変更点は、関数before と一緒です。では、動作を。

> (after 'a 'b '(b a d))
(a d)
> (after 'a 'b '(a))
nil
> (after 'a 'b '(b))
nil

この程度の動作なら、関数member+ は要らないですけどね。
次の関数duplicate でも、関数member+ を使っています。&key オプションの扱いは、前述の関数と同じです。
では、動作を、

> (duplicate 'a '(a b c a d a c))
(a b a c)
> (duplicate 'a '(a b c e d))
nil

さて、リスト検索 の最後は、関数split-if 。毎度おなじみ、内部変数の初期値nil と funcall を外します。もちろん、fn の変名も必要です。
dovalues は、以前に定義したものを使います。
あとは、newlisp-utility.lsp での定義が補ってくれます。
もちろん、組込push の末尾挿入を使って、reverse を省いています。
では、動作を、

> (split-if (fn (x) (> x 4)) '(1 2 3 4 5 6 7 8 9 10))
(1 2 3 4)
> (multiple-value-list (split-if (fn (x) (> x 4)) '(1 2 3 4 5 6 7 8 9 10)))
((1 2 3 4) (5 6 7 8 9 10))
> (multiple-value-list (split-if (fn (x) (> x 4)) (sequence 1 10)))
((1 2 3 4) (5 6 7 8 9 10))
> (multiple-value-list (split-if (fn (x) (> x 4)) (sequence 10 1)))
(nil (10 9 8 7 6 5 4 3 2 1))

戻り値に values を使っていますので、全ての戻り値を見るのに multiple-value-list を使っています。組込sequence は、等差数列のリストを返します。こういう場合に便利です。
ちなみに、values 以外を newLISP だけ書くと

(define (split-if f lst)
  (or (dolist (i lst (and (f i)
                     (values (0 $idx lst) ($idx lst)))))
      (values lst '())))

こんな感じです(笑)。
さて、引き続き、要素を比較する検索関数 は、こうなります。

(define let* letn)
(defun most (f lst)
  (if (null lst)
      (values nil nil)
    (let* ((wins (car lst))
           (max-i (f wins)))
      (dolist (obj (cdr lst))
        (let ((score (f obj)))
          (when (> score max-i)
            (setq wins obj
                  max-i score))))
      (values wins max-i))))

(defun best (f lst)
  (if (null lst)
      nil
    (let ((wins (car lst)))
      (dolist (obj (cdr lst))
        (if (f obj wins)
            (setq wins obj)))
      wins)))

(defun mostn (f lst)
  (if (null lst)
      (values nil nil)
    (let ((result (list (car lst)))
          (max-i (f (car lst))))
      (dolist (obj (cdr lst))
        (let ((score (f obj)))
          (cond ((> score max-i)
                 (setq max-i score
                       result (list obj)))
                ((= score max-i)
                 (push obj result -1)))))
      (values result max-i))))

では、関数most から。
その前に、let* を定義しています。newLISP組込の letn に置き換えるだけですが(笑)。
後は、いつものように、funcall を外し、fn を変名します。また、newLISPには、組込関数max (もちろん、min も)がありますから、内部変数max も max-i に直します。
動作はというと、

> (most length '((a b) (a b c) (a) (e f g)))
(a b c)
> (multiple-value-list (most length '((a b) (a b c) (a) (e f g))))
((a b c) 3)
> (most length '((a b) (a b c) (a) (d e f g)))
(d e f g)
> (multiple-value-list (most length '((a b) (a b c) (a) (d e f g))))
((d e f g) 4)

さて、関数best は、funcall を外し、fn を変名するだけ。
動作は、

> (best > '(3 4 1 5 2))
5
> (best < '(3 4 1 5 2))
1

次に、関数mostn 。これまでの変更と同様です。reverse も省いています。
動作は、こうなります。

> (mostn length '((a b) (a b c) (a) (e f g)))
((a b c) (e f g))
> (multiple-value-list (mostn length '((a b) (a b c) (a) (e f g))))
(((a b c) (e f g)) 3)
> (mostn length '((a b) (a b c) (a) (d e f g)))
((d e f g))
> (multiple-value-list (mostn length '((a b) (a b c) (a) (d e f g))))
(((d e f g)) 4)
> (mostn length '())
nil
> (multiple-value-list (mostn length '()))
(nil nil)

切りがいいので、マッピング からは、次回に。

以上、如何でしょうか?

newLISP マニュアル・アップデート v.10.2.8 rev-3

newLISP マニュアルrev-3 にアップデートされましたので、“newLISP 関数リファレンス・スクリプト” と “日本語併記関数リファレンス” をアップデートしました。

newLISP 関数リファレンス・スクリプト” は、newLISP 関数リファレンスを表示するスクリプトです。newLISP v10.2.7 以降で動作します。

newLISP 関数リファレンス・スクリプト

日本語併記関数リファレンス” は、newLISP マニュアルの関数リファレンスのみ、日本語に翻訳してあります。現在のバージョンは、v.10.2.8 rev 3 です。

日本語併記関数リファレンス

関数リファレンス部分の日本語併記のページは、まだ、rev 1 のままですが、変更箇所は、delete と address の部分だけなので、まだ変更を依頼していません。

以上、如何でしょうか?

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

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

今回は、第4章 の リストに対する操作
リストに作用する小さな関数 は、以下のようになります。

(module "macro.lsp")
(define last1 last)
(macro (single L)
  (and L (list? L) (not (rest L))))
(macro (append1 L Obj)
  (append L (list Obj)))
(macro (conc1 L Obj)
  (extend L (list Obj)))
(macro (mklist Obj)
  (if (listp Obj) Obj (list Obj)))

newLISP に、インライン宣言はありません。
そのため、効率性のために、macro を使います(macro の日本語訳はこちら)。
macro を有効にするためにスクリプトの先頭で

(module "macro.lsp")

としていますが、前に紹介した include を使って、

(include "macro.lsp")

としておけば、二重ロードを防げます。
動作は、

> (single '())
()
> (single 'a)
nil
> (single '(a))
true
> (single '(a b))
nil
> (let (x '(1 2))(println (append1 x 3)) x)
(1 2 3)
(1 2)
> (let (x '(1 2))(println (conc1 x 3)) x)
(1 2 3)
(1 2 3)
> (let (x '(1 2))(println (append1 x 3)) x)
(1 2 3)
(1 2)

こんな感じです。
macro を使いたくない場合は、conc1 以外は、macrodefine に置き換えれば、そのまま使えます。
ただし、conc1 は破壊的関数です。newLISP で破壊的関数を書くには、マクロが必要です。
macro を使いたくない場合、conc1 は、

(define-macro (conc1)
  (letex (_lst0 (args 0)
          _lst1 (args 1))
    (extend _lst0 (list _lst1))))

となります。
newLISP の組込関数last は、Common Lisp の last とは違いって、リストではなく、要素で返します。つまり、last1 と同じ。
では、Common Lisp 流の last を使いたい時は、どうするのか?
インデックス機構を使って、

> (-1 '(a b c))
(c)
> (-2 '(a b c))
(b c)
> (-3 '(a b c))
(a b c)
> (0 '(a b c))
(a b c)
> (1 '(a b c))
(b c)
> (2 '(a b c))
(c)

直接リストから、取り出せます。
ちなみに、Common Lisp 流の last を newLISP で定義すると

(context 'clisp)
(defun clisp:last (lst n)
  (if (consp lst)
      (if (and n (number? n))
          (let (len (length lst))
            (if (< len n) lst
                ((- (length lst) n) lst)))
        (-1 lst))
    lst))
(context MAIN) 

こんな感じ?

続いて、リストに作用する関数の大規模なもの の方です。

(defun longer (x y)
  (labels ((compare (x y)
             (and (consp x)
                  (or (null y)
                      (compare (cdr x) (cdr y))))))
    (if (and (listp x) (listp y))
        (compare x y)
      (> (length x) (length y)))))

(defun filter+ (f lst)
  (local (acc)
    (dolist (x lst)
      (let (val (f x))
        (if val (push val acc -1))))
    acc))

(defun group (src n)
  (if (zerop n) (error "zero length"))
  (labels ((rec (src acc)
             (let ((rests (nthcdr n src)))
               (if (consp rests)
                   (rec rests (cons (subseq src 0 n) acc))
                 (reverse (cons src acc))))))
    (if src (rec src '()) nil))) 

zerop と labels は、newlisp-utility.lsp に定義してありますので、関数longer は、何の変更もなく動きます。
動作はというと、

> (longer '(a b (c (d)) e f g) '(a b (c d) e f g))
nil
> (longer '(a b c d e f g) '(a b (c d) e f g))
true

関数filter+ は、newLISP組込関数に filter があるので filter+ と名前を変えています。
newLISP の組込push は、項目をリストの最後に挿入できるので、reverse を使っていません。

> (filter+ (fn (x) (if (numberp x) (+ 1 x))) '(a 1 b 3 c d 4))
(2 4 5)

newLISP では、λ式に fn が使えるので楽です。また、関数filter+ を使わなくても、

> (replace nil (mapcar (fn (x) (if (numberp x) (+ 1 x))) '(a 1 b 3 c d 4)))
(2 4 5)

で、実現できます(“On Lisp” 後注48より)。
次の関数group は、newLISP にはない関数 nthcdr と subseq を使っています。newLISP ではどちらもインデックス機構で実現できるので、わざわざ組込関数にしていないのでしょう。以下に、それらの定義を記しておきます。

(defun nthcdr (num lst)
  (when (consp lst) (num lst)))

(defun subseq (lst start end)
  (if (nil? end)
      (start lst)
    (let (len (- end start))
      (start len lst))))

また、newLISPには組込関数rest があるので内部変数rest は、rests に変えてあります。
動作はというと。

> (group '(a b c d e f g) 2)
((a b) (c d) (e f) (g))

しかし、newLISP では組込関数explode が関数group と同様な機能を持っています。

> (explode '(a b c d e f g) 2)
((a b) (c d) (e f) (g))

本書にもあるように、関数group は後でも使われるので、以降は、こう定義します。

(define group explode)

さて、リストに対する操作 の最後は、関数flatten と prune 。

(defun flatten (x)
  (labels ((rec (x acc)
             (cond ((null x) acc)
                   ((atom x) (cons x acc))
                   (t (rec (car x) (rec (cdr x) acc))))))
    (rec x '())))

(defun prune (test tree)
  (labels ((rec (tree acc)
           (cond ((null tree) (reverse acc))
                 ((consp (car tree))
                  (rec (cdr tree)
                  (cons (rec (car tree) '()) acc)))
                 (t (rec (cdr tree)
                         (if (test (car tree))
                             acc
                           (cons (car tree) acc)))))))
    (rec tree '())))

関数flatten は、内部関数rec の引数 nil'() に変えてあるほかは、本文と同じです。
動作はというと、

> (flatten '(a (b c) ((d e) f)))
(a b c d e f)

この機能もまた、newLISP で組込関数flat として、用意されています。

> (flat '(a (b c) ((d e) f)))
(a b c d e f)

以降は、こう定義します。

(define flatten flat)

先ほどの 組込関数explode と同様、newLISP には、ユーティリティに相応しい関数が、最初から組み込まれています。
さて、次の関数prune も、関数flatten のときと同様、nil'() に変えてあります。nil のままだと、flattenも同様ですが、nil が cons されてそのままリストに残ってしまいます。また、funcall もはずしてあります。動作はというと、

> (prune evenp '(1 2 (3 (4 5) 6) 7 8 (9)))
(1 (3 (5)) 7 (9))
> (prune oddp '(1 2 (3 (4 5) 6) 7 8 (9)))
(2 ((4) 6) 8 ())

もちろん、#' は要りません。
ちなみに、CommonLisp(xyzzyを含む)で、oddp 使った時は、

> (prune oddp '(1 2 (3 (4 5) 6) 7 8 (9)))
(2 ((4) 6) 8 nil)

となります。意味的には同じですが、気になる方は最後の行を

(replace '() (rec tree '()) nil)))

とすれば見た目も同じになります。どちらをとるかはお好みで。
検索 からは、次回に。

以上、如何でしょうか?

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

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

まずは、ユーティリティの誕生 から。
関数all-nicknames に使われている nconc は、newLISP にはありませんが、第3章(その1)で定義しました。
しかし、ここでは、組込extend の代用で十分です。

(define nconc extend)
(defun all-nicknames (names)
  (if (null names)
      '()
      (nconc (nicknames (car names))
        (all-nicknames (cdr names)))))

nconc(または、extend) の代わりに append を使っても動作は同じです。
“On Lisp”本書でも後に マッピング で解説していますが、副作用の効果を狙った使い方でないからです。
newLISP では、nil'() は同じではありません。
ここでの組込extend の引数はリストなので、空リストの '() が必要になります(組込append の場合も同様)。
また、次の例の mapcan も newLISP には、ありません。そこで、mapcan を定義して、mapcar(newLISP では map) との違いを見ていきましょう。

(defun mapcan ()
  (apply extend (apply map (args))))

この定義自体も、後に マッピング で出てくる定義の newLISP版です。
ただし、度々書いていますが、newLISP では、副作用を持つ関数は書けません。
副作用まで記述するならマクロにすべきです。
その場合は、

(define-macro (mapcan)
  (letex (_body (cons 'map (args)))
    (apply extend _body)))

となります。
しかし、Common Lispのような副作用は出ません。後に マッピング で出てくる mappend と同じ動作になります。
例えば、Common Lisp なら

[1]> (let ((x '((a) 1)) (y '((b) 2)) (z '((c) 3))) (mapcan #'car (list x y z)))
(A B C)
[2]> (let ((x '((a) 1)) (y '((b) 2)) (z '((c) 3))) (mapcan #'car (list x y z)) x
)
((A B C) 1)

となりますが、newLISP では、

> mapcan
(lambda-macro () 
 (letex (_body (cons 'map (args))) (apply extend _body)))
> (let (x '((a) 1) y '((b) 2) z '((c) 3)) (mapcan first (list x y z)))
(a b c)
> (let (x '((a) 1) y '((b) 2) z '((c) 3)) (mapcan first (list x y z)) x)
((a) 1)
> (defun mapcan () (apply extend (apply map (args))))
(lambda () (apply extend (apply map (args))))
> (let (x '((a) 1) y '((b) 2) z '((c) 3)) (mapcan first (list x y z)))
(a b c)
> (let (x '((a) 1) y '((b) 2) z '((c) 3)) (mapcan first (list x y z)) x)
((a) 1)

といった具合に、関数で定義しても、マクロで定義しても、同じ動作になります。
newLISP で定義した nconc のようには副作用まで再現できませんでした(汗)。
何故か?それはまた、別の機会に。

ということで、この章の使い方では、関数版mapcan で十分なのです。
さて、話しを戻して、動作を見るために、関数nicknames を定義します。

> (defun nicknames (name)
  (rest (parse name)))

定義した関数nicknames は、セカンドネームのリストを返します。
最初は、mapcar の例。newLISP では、map を使います。

> (map nicknames '("a 1" "b 2" "c 3"))
(("1") ("2") ("3"))

newLISPでは、引数の関数に #’ を付加する必要がないことに注意して下さい。
各要素毎にリストが作られ、そのリストが返されます。
中の括弧が邪魔? それなら関数all-nicknames を作ればいい。

> (all-nicknames '("a 1" "b 2" "c 3"))
("1" "2" "3")

中の括弧が取り払われます。
でも、mapcan を知っている人は、関数all-nicknames なんかを作らない。

> (mapcan nicknames '("a 1" "b 2" "c 3"))
("1" "2" "3")

“既知の関数を知っているか、いないかは、大きな違いだけれど、何がほしいかが分かっていることはもっと重要” というのが、ここのポイント。
とはいえ、組込関数が高々350個くらいという、newLISP の方が組込関数を知っている点では有利かも(笑)。
上記の結果がほしい時、newLISP では、どうするか?

> (apply extend (map nicknames '("a 1" "b 2" "c 3")))
("1" "2" "3")

あるいは、この場合のように、結果に入れ子の括弧が無いなら、

> (flat (map nicknames '("a 1" "b 2" "c 3")))
("1" "2" "3")

といったところ。この程度なら、mapcan も nconc も要らない?
さて、次の例題で使われている find-if も newlISP には、ありません。
嘘です、あります。&key オプションを除けば、組込exists が find-if 相当です。
これで、例題を記述すると、

(define find-if exists)
(let ((town (find-if bookshops town)))
  (values town (bookshops town))
(defun find-books (towns)
  (if (null towns)
      nil
    (let ((shops (bookshops (car towns))))
      (if shops
          (values (car towns) shops)
        (find-books f (cdr towns)))))) 

values の定義は、こちらにあります。そして、本命の関数find2 は、

(defun find2 (f lst)
  (if (null lst)
      nil
    (let ((val (f (car lst))))
      (if val
          (values (car lst) val)
        (find2 f (cdr lst)))))) 

となります。newLISP では、funcall が要らないことに注意して下さい。また、fn は f にしてあります。
さて、実際に動きを見てみましょう。本屋の代わりにリストの中から数字を探します。

> (defun bookshops (lst) (find-if numberp lst))
(lambda (lst) (find-if numberp lst))
> (setq towns '((city0 a b c)(city1 a 2 c)(city2 1 b c)(city3 a b 3)))
((city0 a b c) (city1 a 2 c) (city2 1 b c) (city3 a b 3))
> (let ((town (find-if bookshops towns))) (values town (bookshops town)))
(city1 a 2 c)
> (multiple-value-list (let ((town (find-if bookshops towns))) (values town (bookshops town))))
((city1 a 2 c) 2)
> (find2 bookshops towns)
(city1 a 2 c)
> (multiple-value-bind (town bookshop) (find2 bookshops towns) (list (town 0) bookshop))
(city1 2)

find-books も find2 も所望の動作をしています。

次は、 抽象化への投資 です。
ユーティリティを作る前に、よく読んでおくことをお勧めします。
さて、特に例題はないのですが、先程の find2 を newLISP だけで書いてみましょう。

(define (find3 f lst)
  (and lst
    (letn (L (lst 0) (val (f L)))
      (if val
          (values L val)
        (find3 f (1 lst))))))

こんな感じでしょうか。car や cdr の代わりに、インデックス機構を使います。

予定外に、長くなってきたので、リストに対する操作 からは、次回に。

以上、如何でしょうか?

init.lsp と include について

(この blog は、“short short story または 晴耕雨読な日々”に掲載したものの再掲です。スクリプトも含め、一部加筆修正してあります。)

newLISP の起動時、起動ディレクトリに init.lsp があると、それを読み込みます。
つまり、 init.lsp を用意することで、お好みの newLISP に変身させられます(笑)。
私が使っている init.lsp は、次のようになっています。

(context 'MAIN:include)
; http://www.newlisp.org/index.cgi?page=Code_Snippets
(define error "")
(define modules '())
(define (include:include mfile)
    (unless (find mfile modules)
      (when (catch (if (file? mfile) (load mfile) (module mfile)) 'error)
        (push mfile modules -1))))

(context 'MAIN:type-of)
; http://static.artfulcode.net/newlisp/util.lsp.src.html
(define types '("boolean" "boolean" "integer" "float" "string" "symbol" "context"
                "primitive" "primitive" "primitive" "quote" "list" "lambda" "macro"
                "array"))
(define (type-of:type-of x)
  (let (type (types (& 0xf ((dump x) 1))))
    (if (and (= "list" type) (context? (first x)))
          (name (first x))
          type)))
(context MAIN)
(include "macro.lsp")
(include "newlisp-utility.lsp")

このように、この blog で紹介するスクリプトの先頭で使っている、あるいは、使うことになる関数include は、私の場合、init.lsp で定義しています。
関数include は、Common Lisp の require に相当します
ここで定義している関数include は、引数として与えられたファイル名のファイルがあれば、それを load し、なければ、環境変数NEWLISPDIR下の modulesディレクトリから load します。
どちらにもなければ、nil を返します。つまり、戻り値を見ることで、正常に読み込まれたかどうかを判定できます。エラー内容は、文字列変数include:error に入ります。
ロードしたファイル名は、リスト変数include:modules に入っています。
また、関数tyoe-of は、文字通り、引数の型を返します。newLISP組込みで入っていても良さそうですが、ArtfulCode の Module: util に定義してあるのを使わせてもらっています。デバック時に重宝します。
あとは、おなじみ、macro.lsp日本語訳) と拙作newlisp-utility.lsp を include しています。
これだけ init.lsp で用意しておけば、以降の blog で紹介するスクリプトがすんなり動きます。(笑)
便利な init.lsp ですが、何らかの事情で、読み込みたくない時は、

newlisp -n

で、起動すれば、素の newLISP が立ち上がります。

以上、如何でしょうか?

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

第3章 関数プログラミング の続きです。
今回は、命令型プログラミングの裏返し から。
newLISP も Common Lisp と同様に、関数型プログラミングと命令型プログラミングの両方で書くことができます。
(defun、car は、newlisp-utility.lsp に定義してあります)

(defun fun (x)
  (list 'a (pow (car x) 2)))

(defun imp (x)
  (local (y sqr)
    (setq y (car x))
    (setq sqr (pow y 2))
    (list 'a sqr)))

組込local は、len と同じ機能で、引数のシンボルを全て nil で初期化します。
“On Lisp” 本書にある命令型プログラムを裏返しながら関数型プログラムへ変更していく過程は、newLISP でも一緒です。
動作は、試すまでも無いですね。

次の 関数的インターフェイス にある副作用の問題は、newLISP では当てはまらないかもしれません。
最初の二つの例は、

(defun qualify (expr)
  (nconc (copy expr) (list 'maybe)))

(context 'total)
  (setq x 0)
  (defun total:total (y)
    (++ x y))
(context MAIN)

となり、動作は、

> (qualify '(x))
(x maybe)
> (total 1)
1
> (total 3)
4
> (total 5)
9

こんな感じ。
そして、nconc の使い方、
(nconc は、第3章(その1) で定義しています)

(defun ok (x)
  (nconc (list 'a x) (list 'c)))

(defun not-ok (x)
  (nconc (list 'a) x (list 'c)))

の動作は、

> (setq x '(b))
(b)
> (ok x)
(a (b) c)
> x
(b)
> (not-ok x)
(a b c)
> x
(b)

どちらも、引数に副作用を及ぼしません。
前回書きましたように、newLISP では、通常、副作用を起こす関数は定義できないからです。
“通常”としたのは、特殊な方法で書くこともできるからで、それはまた別の機会に。
もちろん、

(defun anything (x)
  (+ x *anything*))

も副作用は起こしませんし、

(defun exclaim (expression)
  (append expression '(oh my)))

を使って、

> (exclaim '(lions and tigers and bears))
(lions and tigers and bears oh my)
> (extend (exclaim '(lions and tigers and bears)) '(goodness))
(lions and tigers and bears oh my goodness)
> (exclaim '(fixnums and bignums and floats))
(fixnums and bignums and floats oh my)

としても、Common Lispのような副作用は起きません。
もちろん、

(defun exclaim (expression)
  (append expression (list 'oh 'my)))

でも、問題はありません。すべて、マクロでなく関数定義だからです。
関数がクォート付きリストを返すべきでないというルールは、newLISPでは、意味が無いかもしれません。

最後の インタラクティブプログラミング の内容は、newLISP にも、そのまま当てはまります。
副作用を起こす関数が書けないという意味では、newLISP の方が有利かもしれません。
デザイン・ルールで気を付けなければいけないのが、“関数”ではなく“マクロ”のみで済む、という意味で。

さて、第3章 関数プログラミング のまとめです。

  • newLISP でも、関数型プログラミングと命令型プログラミングの両方で書くことができます。
  • newLISP では、副作用を起こす関数は定義できません。(一部例外あり
    ただし、マクロでは、副作用を起こすことが可能です。
  • newLISP には、多値の戻り値はありません。
    ただし、多値関数の実装は可能です。

以上、如何でしょうか?

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

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

以上、如何でしょうか?