Schemeとマクロ

Schemeにはマクロ展開時の変数名の衝突を自動的に回避してくれる健全なマクロが用意されている。(意識的に衝突を起こしたり、展開時の環境に変数を束縛したりしたい場合は、処理系によっては、explicit renamingによるマクロやdefine-macroによるマクロなども利用できたりする。) syntax-rulesは独自のパターンマッチ言語を使ってコードを置き換える感じなので、簡単だがScheme自体でリストを操作していないという不満もあるらしい。R7RS-largeで入るとうわさされているexplicit renamingによるより低レベルなマクロではリストをより直接操作できる。

最近の言語では大抵用意されていたりするリストの内包表記。SchemeでもSRFI 42に用意されているが、なんとなく馴染む構文が欲しかったので実装してみた。また、Haskellのように[1..10]みたいにリスト生成したり、Fortranのようにa(2:5)でリストのスライスが欲しかったりしたのでこれも書いてみた。

( import ( scheme base ) ( srfi 1 )) ( define-syntax l: ( syntax-rules ( .. ) (( _ e ) ( l: 0 .. ( - e 1 ))) (( _ s .. e ) ( if ( > e s ) ( l: s ( + s 1 ) .. e ) ( l: s ( - s 1 ) .. e ))) (( _ s t .. e ) ( let (( diff ( - t s )) ( cp ( if ( > t s ) > < ))) ( let loop (( l `(, s )) ( c t )) ( if ( cp c e ) ( reverse l ) ( loop ( cons c l ) ( + c diff )))))))) ( define-syntax l/s ( syntax-rules ( : ) (( _ l s : e ) ( l/s ( l/s l : e ) s : )) (( _ l s : ) ( drop l s )) (( _ l : e ) ( take l ( + e 1 ))) (( _ l i ) ( list-ref l i )))) ( define-syntax l/c ( syntax-rules ( : <- .. := ) (( _ lm : ( x <- xs ) lc ... ) ( apply append ( map ( lambda ( x ) ( l/c lm : lc ... )) xs ))) (( _ lm : ( x <- s .. e ) lc ... ) ( l/c lm : ( x <- ( l: s .. e )) lc ... )) (( _ lm : ( x <- s t .. e ) lc ... ) ( l/c lm : ( x <- ( l: s t .. e )) lc ... )) (( _ lm : ( d := ds ) lc ... ) ( let (( d ds )) ( l/c lm : lc ... ))) (( _ lm : c lc ... ) ( if ( not c ) '() ( l/c lm : lc ... ))) (( _ lm : ) `(, lm ))))

実行例。

( l/c ( list a b c area ) : ( a <- 1 .. 10 ) ( b <- 1 .. a ) ( c <- 1 .. b ) ( = ( * a a ) ( + ( * b b ) ( * c c ))) ( area := ( / ( * b c ) 2 ))) => (( 5 4 3 6 ) ( 10 8 6 24 )) ( l: 10 ) => ( 0 1 2 3 4 5 6 7 8 9 ) ( l: 1 .. 3 ) => ( 1 2 3 ) ( l: 2 4 .. 10 ) => ( 2 4 6 8 10 ) ( l/s '( 1 2 3 4 5 ) 1 : 3 ) => ( 2 3 4 ) ( l/s '( 2 4 6 8 10 ) 2 : ) => ( 6 8 10 )

引数の評価については考えていないが、副作用を使わなければ大丈夫なはず。とりあえず動くので良しとしよう。こんな風に簡単に言語機能自体を拡張できるのを見るとやっぱりLispは楽しいと思う。