Archive for 2010年6月19日|Daily archive page
newLISP で On Lisp する...第2章(その4)
(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
On Lisp 第2章 も その4 まで来てしまいました(汗)。
今回は、ローカル関数 からです。ここでのメインは、labels の紹介です。ところで、皆さんは、labels をお使いでしょうか? 恥ずかしながら、私はあまり使っていません。“mapcarの第1引数に再帰関数を与えたい時” は、defun で定義しています。また、“ローカルな束縛を必要とし、かつ再帰的な関数を mapcar に与えたい時” は、後述しますが、curry を使います。だから、あまり必要としなかったわけです。
前置きはさておき、newLISP には、labels はありません。
それで終わってしまっては、On newLISP にならないので、マクロを組みましょう。
(newlisp-utility.lsp にも定義してあります。)
(define-macro (labels) (letex (_labels (append '(begin) (map (fn (x) (list 'setq (x 0) (append '(fn) (list (x 1)) (2 x)))) (args 0)) (1 (args)))) _labels))
え、定義できるの?と思われた方、私もそうでした。結論からすると定義できます。
実際に動かしてみましょう。(i+ は、newlisp-utility.lsp に定義してあります)
> (labels ((incx (x) (i+ x))) (incx 3)) 4
newLISPには、組込関数inc がありますので、incx に変えてあります。また、1+ は i+ にしてあります。
では、関数に組み込んでも動くでしょうか。
見てみましょう。(defun は、newlisp-utility.lsp に定義してあります)
(defun count-instances (obj lsts) (labels ((instances-in (lst) (if (and (list? lst) (true? lst)) (+ (if (= (car lst) obj) 1 0) (instances-in (cdr lst))) 0))) (map instances-in lsts)))
newLISP には consp がありませんので、list? と true? を組み合わせて代用しています。もちろん、mapcar は map に変えてあります。 car と cdr もありませんが、newlisp-utility.lsp に定義してあります。使いたくなければ、それぞれを first と rest に書き換えてください。
では、動かしてみましょう。
> (count-instances 'a '((a b c) (d a r p a) (d a r) (a a))) (1 2 1 2)
所望の動作をします。ただし、newLISP は、ダイナミック・スコープですから、過信は禁物です。思わぬバグを引き起こすかもしれません。ご注意を。
(newlisp-utility.lsp にある labels は、context 内で定義してあるのでより安全です。)
ここからちょっと番外です。関数count-instances のようなタイプを作りたい時は、私はこうしています。まず、内部の再帰関数を定義します。
(consp は、newlisp-utility.lsp に定義してあります。)
(defun instances-in (obj lst) (if (consp lst) (+ (if (= (lst 0) obj) 1 0) (instances-in obj (1 lst))) 0))
当然、変数は2つ必要ですよね。これを map に与える際、次のようにします。
> (map (curry instances-in 'a) '((a b c) (d a r p a) (d a r) (a a))) (1 2 1 2)
と、まあ、こんな感じで、newLISP で習い覚えた curry を使います。xyzzy用にも移植しました。ローカルな束縛が、第1引数だからいいものの、第2引数以降だったら? もちろん、大丈夫です。hayashi がありますから(笑)。ダイナミック・スコープの newLISP では、こちらの方が良いかも。とはいえ、labels を否定するものではありません。あれば便利です。ただ、便利だからといって、使うかどうかは別です。必要かどうかですよね。
さて、本題に戻りましょう。次は、末尾再帰 です。
まずは、末尾再帰でない例から、1+ は i+ にして、
(null は newlisp-utility.lsp に定義してあります)
(defun our-length (lst) (if (null lst) 0 (i+ (our-length (cdr lst)))))
そして、末尾再帰の例、 fn は f にして、
(defun our-find-if (f lst) (if (f (car lst)) (car lst) (our-find-if f (cdr lst))))
さらに、labels を使った例、
(defun our-length2 (lst) (labels ((rec (lst acc) (if (null lst) acc (rec (cdr lst) (i+ acc))))) (rec lst 0)))
labels を定義していたので、順調に進みます。これでもよいのですが、私の場合は、以下のように書くことが多いです。
(defun our-length3 (lst (acc 0)) (if (null lst) acc (our-length3 (1 lst) (i+ acc))))
CommonLisp でも第2引数に &opional を使えば、同じように書けます。では、何故 labels を使うのか?
それは置いといて、newLISP には、optimize や fixnum のような宣言はありません。
関数triangle は、以下のようになります。
(defun triangle (n) (labels ((tri (c n) (if (zerop n) c (tri (+ n c) (- n 1))))) (tri 0 n)))
これで 末尾再帰 は終わりです。
私も、再帰関数は末尾再帰になるように心掛けてはいます。
でも、labels は使っていません。上記triangle は、私が書くとこうなります。
(defun triangle2 (n (c 0)) (if (zerop n) c (triangle2 (- n 1) (+ n c))))
スクリプトの構造を見渡すには、labels を使った方が見やすいのかもしれません。特に、λ式を多用する人にとっては。そうでしょう?
さて、次は、コンパイル。
残念ながら、ここでは、何もありません。newLISP には、コンパイラがありませんので。。。
そして、最後に、リストから作られる関数。ここでも、例題はありませんが、
“LispプログラムがLispプログラムを書けるということは,どんなに強調しても強調し過ぎにはならない.”
という言葉は、Lisp を newLISP に変えても当てはまるでしょう。
ちなみに、newLISP では、関数をリストとして扱えます。これも、newLISP の特徴です(笑)。
長かった第2章もようやく終わり、まとめです。
- newLISP では、関数の定義には、define を使います。
(補足::本 blog では、マクロで組んだ defun を使っています。) - newLISP では、λ式に、lambda も fn も使えます。
- newLISP では、関数と変数の名前空間は同一です。したがって、#’ も funcall も要りません。
- newLISP には、ドット対がありません。
- newLISP には、属性がありません。
ただし、context を使うことで、属性相当の記述は可能です。 - newLISP は、ダイナミック・スコープです。
ただし、context を使うことで、レキシカル・スコープ同等の記述は可能です。 - newLISP でも、labels は実装できます。
- newLISP には、コンパイル機能はありません。
ただし、コンパイルしなくても高速です。また、単独で起動できる exeファイルを作ることができます。 - newLISP では、関数をリストとして扱えます。
以上、如何でしょうか?