Kyoto.Lisp Hackathon で作ったけどボツになったコード
Kyoto.lisp Hackathon #1 に参加してきた。
話としては(2012-08-11Nendo*Lisp Kyoto.Lisp Hackathon でやったことのまとめ) の続き。
本エントリの目的は、Hackathon当日の作業のうち直近ではボツだが、今後再利用できそうなテクニックをメモしておくこと。 次のような場面で使うスペシャルフォームをマクロ定義した。
ちょっと恣意的な例ではあるが、長いベクタに対して高階関数による多段処理をする例だ。 大きなテキストファイルをまるごと読み込んだArrayも長いベクタになるので、実用的なプログラミングでも良く出てくるパターンだ。 入力データは巨大だが、最終的に takeメソッドで最初の数要素のみ使い、あとは不要な場合は遅延評価を使って無駄な計算は省きたい。
(use srfi-1)
(let (*vec (Range.new 0 1000000)*)
(for-each
print
(.
(map
(lambda (x) (sprintf "<%05d>" x))
(filter
(lambda (num) (< num 100))
vec))
take 5)))
最後から2行目の vec を vec.lazy もしくは (lazy-vector vec)にすれば遅延評価を実現できるが、どこにlazyを指定するのが適当かを考えるのが面倒だ。 できれば、次のように(lazy S式)で囲めばその範囲で可能な限りよろしくやってくるのが嬉しい。(schemeのlazyと予約語がぶつかっているので、lazy-realmとかにしたほうが良いかな…)
(use srfi-1)
(lazy ;; この中のvector(すなわちRubyのArray型)は全てlazyに扱われる
(let (*vec (Range.new 0 1000000)*)
(for-each
print
(.
(map
(lambda (x) (sprintf "<%05d>" x))
(filter
(lambda (num) (< num 100))
vec))
take 5))))
それを実現するのがHackathon当日に書いた次のマクロ。 健全なマクロを使っている。コードが長いので、gistに貼りつけた。 lazy special form sample — Gist
あとは%lazy-map、%lazy-for-each、%lazy-filterを関数で実装すれば、(lazy S式)で囲った中身のmap、for-each、filterの実装をlazy版に差しかえることができる。 便利そうな気もするが、影響範囲が大きいわりにはメリットが少ないような気もして、現段階では自分で(lazy-vector vec)する上の案に落ちついた。 それに、Nendoのsyntax-rulesはchibi-scheme 0.3からsyntax-rulesの実装のポーティングだが、ネストした “…” が扱えないという問題もあり、すっきり定義できていない。 このあたりも今後の課題として残る。
ただ、このテクニックを使ってpure(副作用無しのコードブロック宣言)も実現できるんじゃないかと考えているので、今後はそちらを試す予定。 pureのアイデアについてはこちらの過去記事を参照のこと。 2011-10-07Nendo*Ruby コードブロックに副作用が混在するかを検出できるかどうか こういう、できるかどうかわからないけど試す価値のある実験は1日かけて作業するHackathonが向いていると思った。