Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2008-04-28

CLで学ぶ「プログラミングGauche」 (7.3〜7.6)

| 04:36 | CLで学ぶ「プログラミングGauche」 (7.3〜7.6) - わだばLisperになる を含むブックマーク はてなブックマーク - CLで学ぶ「プログラミングGauche」 (7.3〜7.6) - わだばLisperになる

今回は、7.3からの再開です。

7.3 ローカル変数

このセクションで、lambdaとletの関係の説明がでてきます。

CLではlet、let*はlambdaの糖衣構文ではなく、スペシャルフォームです。

歴史的には、MacLISP等でもletはlambdaの糖衣構文として登場したようで、Schemeの方が伝統に忠実といったところかもしれません。

CLもレキシカルスコープなのでletとlet*の機能については、ほぼ同じです。

letrecですが、CLには、let、let*からの延長としてのletrecに正確に相当するものはなく、似たところでは、labelsでしょうか。

labelsで置き換えると

(labels ((sum (lst)
	   (cond ((null lst) 0)
		 ((numberp (car lst)) (+ (car lst) (sum (cdr lst))))
		 ('T (sum (cdr lst))))))
  (sum '(1 3 #:f 6 #:t 9)))
;=> 19

;; ローカルな相互再帰
(labels ((even? (n)
	   (cond ((zerop n) 'T)
		 ((> n 0) (odd? (1- n)))
		 ('T (odd? (1+ n)))))
	 (odd? (n)
	   (cond ((zerop n) nil)
		 ((> n 0) (even? (1- n)))
		 ('T (even? (1+ n))))))
  (even? 10))
;=> T

のようになります。

labelsは、CL以前のMacLISP系処理系には存在しませんでした。

意外にも初期のSchemeに存在していたりするのですが、CLのlabelsは、Scheme由来なのかもしれません。

Schemeではletrecに吸収されたのか、消えてしまいましたが…。

何れにせよ、どちらも大元は、LISP1.5のLABELに由来するものだとは思います。

7.4 可変長引数を取る

可変長引数については、CLとSchemeとでは違っていて、引数の性質については、ラムダリストパラメータで指定します。

MacLISP系は大体共通でEmacs Lispでも大体同じです。

ラムダリストパラメータを使わない場合は、Schemeと同じくすべて必須引数になります。

指定した場所以降を纏めてリストにして受け取りたい場合は、&restをつけます。

;; Scheme
((lambda (a . b) (list a b))
 1 2 3)
;=> (1 (2 3))

;; CL
((lambda (a &rest b) (list a b))
 1 2 3)
;=> (1 (2 3))

Scheme風にドット表記も使える局面はあることはあるのですが、ラムダリストパラメータも使えるので、トリビアとして憶えておく位かもしれません。

(destructuring-bind (a . b) '(1 2 3)
  (list a b))
;=> (1 (2 3))

defmacroの引数等でもこういう風に書くことは可能ではあります。

[練習問題]

(defun my-list (&rest args)
  args)

(MY-LIST 1 2 3 4)
;=> (1 2 3 4)

SBCLでもLISTの実装はこのまんまなコードです。

しかし、CLでは、&restで受けとる引数リストが新規に作成されることを義務づけているわけではないので、処理系によっては、このコードではまずいらしいです。*1

具体的には、applyの引数にした場合、元がリストで与えられるので問題になります。

ということは、SBCLでは、&restで受けとったパラメータは新規にコンスされたことが保証されているのでしょう。

(let ((lst '(1 2 3 4 5))
      (lst2 '(i ii iii)))
  (nconc (apply #'my-list lst) lst2)
  lst)
;=> (1 2 3 4 5)

;処理系によっては、
;=> (1 2 3 4 5 I II III)
;でも良いらしい。

X3J13では議論の結果、主にパフォーマンスの問題からこのように決定されたようです。

パフォーマンスと引き換えに、プログラマは、&restパラメータを破壊するような操作はしないように気を付ける必要があります。

とはいえ、コンピュータのパフォーマンスはどんどん上がってきているので、今どきは、&restは新規にコンスされているのが殆どのようです。

7.5 可変長引数を渡す

可変長引数を渡す際にapplyを使う、というのはCLでもリストで受け取ることになるので手法として共通です。

(defun append/log (&rest args)
  (format t "ARGS=~A~%" args)
  (apply #'append args))

(append/log '(a b c) '(1 2 3) '(7 8 9))
;>>> ARGS=((A B C) (1 2 3) (7 8 9))
;=> (A B C 1 2 3 7 8 9)

7.6 引数のパターンマッチング

lengthを使うのが不経済な場合がある(引数が何万もあったら?)ということでmatchが登場します。

Gaucheのutil.matchで採用されているのはAndrew Wright氏のmatcherらしいですが、近い感じのものとしては、FARE-MATCHERがあるようです。

FARE-MATCHERはWright氏のmatchとは書法が若干違って、パターン部がなんというか「動的な表現?」になっています。

話の流れ上、lengthが不経済な局面でmatchの実装がlengthより経済的でなければならない訳ですが、ちょっと確認してみました。

(use-package "FARE-MATCHER")

(let ((lst (loop :for i :from 0 :to 10000000 :collect i)))
  (values
   (time
    (case (length lst)
      (0 ())
      (1 'one!)
      (otherwise 'many!)))
   (time
    (match lst
      (() ())
      ((list a) 'one!)
      (_ 'many!)))))
;>>> length  0.039 seconds of real time
;>>> match  0.0 seconds of real time

要素数を10,000,000位にしないと、はっきりとした差は出ませんが、とりあえず、この局面では、FARE-MATCHERでも効率が良いようです(当たり前か(笑))

ということで、FARE-MATCHERのmatchを使うことにしてみました。

(defun append2 (a b)
  (if (consp a)
      (cons (car a) (append2 (cdr a) b))
      b))

(defun my-append (&rest args)
  (match args
    (() ())
    (`(,a) a)
    (`(,a ,@b) (append2 a (apply #'append b)))))

;; もしくは
(defun my-append (&rest args)
  (match args
    (() ())
    ((list a) a)
    ((cons a b) (append2 a (apply #'append b)))))

(MY-APPEND '(1 2 3 4) '(i ii iii iv) '(a b c d))
;=> (1 2 3 4 I II III IV A B C D)

のように書けます。

matchは便利なのでGaucheが羨しかったのですが、とりあえずfare-matcherをどしどし使って行くことにしました。