Archive for 2010年6月30日|Daily 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)は、ありません。全て文字列で対応します。

以上、如何でしょうか?