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 に定義してあります。使いたくなければ、それぞれを firstrest に書き換えてください。
では、動かしてみましょう。

> (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 では、関数をリストとして扱えます。

以上、如何でしょうか?