日本 Ruby 会議 2007 - Log0610-S5
RubyKaigi でみんな感動したと噂の Dave Thomas さんの講演のログ。やっぱり内容自体はどうでもよくて、ここが気になりました。
たとえば list comprehension がほしい。
Haskell の内包表記 (内包表現) とか確かにかっこいいけれど、Ruby の文法に Haskell の内包表記の文法を入れるのは明らかに無理だし、Python みたいな文法 ([x**2 for x in range(10)] みたいなの) は勘弁して欲しいですよね。それに、今の Ruby でも (ネタの範疇なら) 似たようなことができそう。そこで、以下のようなコードが動くようなものを作ってみました。
# [ x^2 | x <- [0..10] ] みたいなもの p list{ x ** 2 }.where{ x.in(0..10) } #=> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100] # [ [x, y] | x <- [0..2], y <- [0..2], x <= y ] みたいなもの p list{ [ x, y ] }.where{ x.in(0..2); y.in(0..2); x <= y } #=> [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]] # sieve (x:xs) = x:sieve [ y | y <- xs, y `mod` x /= 0 ] みたいなもの def sieve(x, *xs) ys = list{ y }.where{ y.in(xs); y % x != 0 } [x] + (ys.empty? ? [] : sieve(*ys)) end p sieve(*(2..50)) #=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
where より when のが適切? あと、やっぱり内包表記は無限リストがないとあんまり生きてこない気がします。
以下コード。
ポイントは以下の 2 つかな。
- ブロックを define_method することで未定義変数の参照を method_missing で拾う
- callcc で条件を満たすものを列挙する (まじめに考えてないのでバグあるかも)
class ListComprehension def initialize(&blk) plant(:value, blk) end attr_accessor :ctn # ブロックを真にするものだけリストアップする def where(&blk) plant(:cond, blk) ary = [] @table = {} @state = :cond callcc do |ctn| @ctn = ctn if cond @state = :value ary << value @state = :cond end @ctn.call end ary end # ブロックをメソッドとして埋め込む # (未定義変数の参照を method_missing で拾うため) def plant(m, b) (class << self; self; end).class_eval { define_method(m, &b) } end def method_missing(m) r = (@table[m] ||= Slot.new(self)) @state == :cond ? r : r.val end class Slot # 別のオブジェクトになるべくなりすますクラス instance_methods.each do |m| define_method(m) do |*a| @val.send(m, *a) end end def method_missing(*a, &b) @val.send(*a, &b) end def initialize(lc) @lc, @val = lc, nil end attr_reader :val # なりすますオブジェクト def in(ary) ctn = @lc.ctn ary.each do |e| callcc do |c| @lc.ctn = c @val = e return true end end ctn.call end def is(e) @val = e end end end def list(&b) ListComprehension.new(&b) end