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-06-25

サンプルコードによるLOOPマクロ入門 (1)

| 17:18 | サンプルコードによるLOOPマクロ入門 (1) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (1) - わだばLisperになる

LOOPマクロはCLerのなかでも賛否両論な感じなのですが、それなりに使われていることもあり、簡単なものの読み書き位はできるようになっておいたほうが良いかと思います。

複雑という評判だけに、色々とチュートリアルがあるのですが、実際のところ「どう使うのか」というよりは、機能を列記しているものが多いようで、リファレンス的な内容になっているものが多いようです。

ということで、レシピブックのように使い方に焦点を絞って、あまり使いそうもない機能は、応用ということで紹介してゆくのも良いかと思いました。

タイトルは、Perl入門ゼミさんをパクリました(笑)

LOOPマクロの構文

LOOPマクロは、ミニ言語と呼ばれるようにLOOPの節の中で一つの世界を形成しています。

どういう世界かといえば、中置記法が使える世界で、また、英文としても読み下しやすいように配慮されています。

ということで、LOOPマクロの文法をそれなりに憶える必要があり、色々組み合わせると複雑にもなるのですが、普段使うものは、比較的少なく、数種類のパターンを丸暗記してしまえば、9割方読み書きできるような気がするので、そんなパターンを列記して行くことにします。

見やすさの工夫

LOOPマクロは、中置記法を実現するために色々なキーワードを使っています。

(loop for i in '(1 2 3 4) collect (* i i))

という場合、for、in、collectはキーワードで、iは変数名なのですが、

人によっては、読みやすいようにキーワードを目立つように書く人もいます。

  • キーワードパッケージのシンボルでキーワードを書いた例(ややこしい表現)
(loop :for i :in '(1 2 3 4) :collect i)
  • キーワードを大文字にしてみた例(非常にまれ)
(LOOP FOR i IN '(1 2 3 4) COLLECT (* i i))

LIST -> LISTパターン (in)

LISPだけにリストを操作するパターンが多いと思うのですが、まず、このパターンを紹介してみます。

暗記するキーワードは、forと、inと、collectです。

(loop :for i :in '(1 2 3 4) :collect i)
;=> (1 2 3 4)

'(1 2 3 4)というリストを取って、'(1 2 3 4)というリストを返します。

同じような内容の処理は、

(mapcar (lambda (x) x) '(1 2 3 4))
;=> (1 2 3 4)

とも書けます。

繰り返しの内容としては、'(1 2 3 4)というリストの要素を先頭から順にiに格納し、繰り返します。

それで、:COLLECTというのは、値を収集するという指定で、繰り返しが終わると、その蓄積結果を全体の結果として返します。

ちなみに、:COLLECTを使わないLOOPマクロは、使ってもあまり旨味がないことが殆どで、他の繰り返し構文の方が綺麗に書けることが多いと思います。

応用(COLLECT節で色々実験する)

(loop :for i :in '(1 2 3 4) :collect (list i))
;=> ((1) (2) (3) (4))

(loop :for i :in '(1 2 3 4) :collect (* i i))
;=> (1 4 9 16)

(loop :for i :in '(1 2 3 4)
      :collect (list i '=> (format nil "~R" i)))
((1 => "one") (2 => "two") (3 => "three") (4 => "four"))

;; 元リストと同じ長さで内容がGENSYMのリストを返す
(loop :for i :in '(1 2 3 4) :collect (gensym))
;=> (#:G3362 #:G3363 #:G3364 #:G3365)

他の構文との比較

(loop :for i :in '(1 2 3 4) :collect (list i))
;=> ((1) (2) (3) (4))

;; mapcar
(mapcar #'list '(1 2 3 4))

;; do
(do ((i '(1 2 3 4) (cdr i))
     (res () (cons (list (car i)) res)))
    ((endp i) (nreverse res)))

;; dolist
(let (res)
  (dolist (i '(1 2 3 4) (nreverse res))
    (push (list i) res)))

;; reduce
(reduce (lambda (i res) (append (list i) res))
        '(1 2 3 4) :from-end 'T :initial-value () )

;; reduce #2
(nreverse
 (reduce (lambda (res i) (cons (list i) res))
         '(1 2 3 4) :initial-value () ))
  • mapcar
    • この例の場合、一番シンプル
  • do
    • 蓄積用の変数resを用意しないといけない
    • cdrを降りて行くので、リストのCARを取得する必要がある。
    • consして、最後にnreverseするのがイディオムだが、面倒といえば面倒(appendして順番を保持しても良いが、cons&nreverseの方が効率が良い)
  • dolist
    • 蓄積用の変数resを用意しないといけない
    • 結果をreverseする必要がある
  • reduce
    • do、dolistと同様に自由度は高いが、この程度の処理の場合、逆に繁雑

ゲスト



トラックバック - http://cadr.g.hatena.ne.jp/g000001/20080625