srfi-42の紹介(loopマクロ:scheme版のような)
はじめに
多くの言語はちょっとしたループを回すような処理を手軽に記述する方法を持っています。CLにはloopマクロ*1がありますし、pythonやhaskellなどはリスト内包表記などを持っています。ちなみにschemeにもそんな方法があります。srfi-42で定義されている"Eager Comprehensions"を使います。名前の通り、haskellのそれとは異なりeagerに(正格に)評価します。
たまたま、arielのlisp脳の勉強会?のustでloopマクロに言及した箇所があったので、scheme版のloopマクロ*2とでも言えるsrfi-42の利用例をちょっとだけ紹介してみます。
以降はschemeの処理系としてgaucheを利用しています。srfi(Scheme Requests for Implementation)は言語仕様(RnRS)の外にはありますが、多くの処理系が対応していることから、schemeの標準ライブラリと考えて良いです。なので、他の処理系でもライブラリの読み込み部分以外は同様の記述で動くはずです。
利用例
srfi-42のライブラリを読み込むとfoo-ecというマクロがつかえるようになります。(foo=list,sum,append...)ここでは、一番わかりやすいlist-ecを使うことにします。
1~9までのlistを作る。
だいたい(list-ec
(list-ec (: i 10) i) ; => (0 1 2 3 4 5 6 7 8 9)
ループの初期値も設定できます(defaultは0)。
(list-ec (: i 5 10) i) ; => (5 6 7 8 9)
もちろん、増減幅も設定できます(defaultは1)。
(list-ec (: i 1 10 3) i) ; => (1 4 7)
二乗した値のlistを作る
(list-ec (: i 10) (* i i)) ; => (0 1 4 9 16 25 36 49 64 81)
条件による絞り込み(奇数のみ集める)
リスト内包表記なので条件式を渡せます。省略していましたが、実際のところは(list-ec
(list-ec (: i 10) (if (odd? i)) i) ; => (1 3 5 7 9) (list-ec (: i 10) (not (even? i)) i) ; => (1 3 5 7 9)
途中式を一時変数に格納(:letの利用)
普通のS式のように途中の値を変数に束縛することも可能です。以下は二乗した結果が偶数なもののみを集めたリストです。
(list-ec (: i 10) (:let sq (* i i)) (if (even? sq)) sq) ; => (0 4 16 36 64)
組のリストの作成(複数のを利用)
(list-ec (: e '(a b c d)) (list e)) ; => ((a) (b) (c) (d))
複数の
(list-ec (: i '(1 2 3)) (: j '(a b c)) (cons i j)) ; => ((1 . a) (1 . b) (1 . c) (2 . a) (2 . b) (2 . c) (3 . a) (3 . b) (3 . c))
map,for-each,foldと同様に並行的に走査して欲しい場合には:pararelでwrapします。
(list-ec (:parallel (: i '(1 2 3)) (: j '(a b c))) (cons i j)) ; => ((1 . a) (2 . b) (3 . c))
外側の変数を中のループで使うことも可能です。
(list-ec (: i 3) (: j i 3) (cons i j)) ; => ((0 . 0) (0 . 1) (0 . 2) (1 . 1) (1 . 2) (2 . 2))
その他foo-ec
(do-ec (: i 10 100 10) (print i)) (list-ec (: i '((a b c) (d e f))) i) ; => ((a b c) (d e f)) (append-ec (: i '((a b c) (d e f))) i) ; => (a b c d e f) (vector-ec (: i 10 0 -1) (if (even? i)) i) ; => #(10 8 6 4 2) (vector-of-length-ec 10 (: i 10) i) ; => #(0 1 2 3 4 5 6 7 8 9) (sum-ec (: i 11) i) ; => 55 (fold-ec 0 (: i 11) i (lambda (i n) (+ i n))) ; => 55 (product-ec (: i 1 11) i) ; => 3628800 (fold-ec 1 (: i 1 11) i (lambda (i n) (* i n))) ; => 3628800 (min-ec (: i '(10 20 -30 40 50)) i) ; => -30 (max-ec (: i '(10 20 -30 40 50)) i) ; => 50 (every?-ec (: i '(1 2 3 4)) (positive? i)) ; => #t (every?-ec (: i '(1 2 3 -4)) (positive? i)) ; => #f (any?-ec (: i '(1 2 3 4)) (positive? i)) ; => #t (any?-ec (: i '(1 2 3 -4)) (positive? i)) ; => #t (first-ec #f (: i '(1 2 -3 4 -5)) (if (negative? i)) i) ; => -3 (first-ec #f (: i '(1 2 3 4 5)) (if (negative? i)) i) ; => #f (last-ec 'foo (: i '(1 2 -3 4 -5)) (if (negative? i)) i) ; => -5 (last-ec 'foo (: i '(1 2 3 4 5)) (if (negative? i)) i) ; => foo
vector-ecよりはvector-of-length-ecの方が高速です。ただし、事前に生成されるvectorのサイズを指定しなければなりません。
活用事例
組み合わせの計算などが簡単に記述できます。
(use srfi-1) (use srfi-42) (define (combinations xs n) (cond ((= n 1) (map list xs)) (else (list-ec (: i xs) (: es (combinations (delete i xs) (- n 1))) (cons i es))))) (combinations '(a b c) 2) ; => ((a b) (a c) (b a) (b c) (c a) (c b))
でもcall/ccを使わなければ途中で止められません。