Rubyで内包表現

Haskellの内包表現を知ってRubyでも使いたくなった。試しに実装。


require 'active_support/binding_of_caller'

class Naiho
attr_reader :element, :bind, :range
def initialize(element, bind, range)
@element = element
@bind = bind
@range = range
end

def to_list(restrictions, binding)
eval(<<-NAIHO, binding)
#@range.select {|#@bind| #{restrictions.join('&&')} }.map do |#@bind|
#@element
end
NAIHO
end
end

class String
def |(other)
raise unless other =~ /^(.+)<-(.+)$/
Naiho.new(self, $1, $2)
end
end

$n = Object.new
def $n.[](naiho, *restrictions)
raise unless naiho.instance_of?(Naiho)
Binding.of_caller do |binding|
naiho.to_list(restrictions, binding)
end
end


=begin
class Array
def initialize(args)
if args.first.instance_of?(Naiho)
naiho, *restrictions = args
Binding.of_caller do |binding|
naiho.to_list(restrictions, binding)
end
else
super
end
end
end
=end

例1:


xs = [10,20,3,-5,1,10,100]
$n['x*2' | 'x <- xs', 'x <= 10']
=> [20, 6, -10, 2, 20]

例2:


def quicksort(x = nil, *xs)
return [] if x == nil
quicksort(*$n['y' | 'y <- xs', 'y < x']) + [x] + quicksort(*$n['y' | 'y <- xs', 'y >= x'])
end
quicksort(*xs)
=> [-5, 1, 3, 10, 10, 20, 100]

ほんとは、Arrayのinitializeを上書きできれば以下の式で済むんだけど、配列式の場合は Arrayのinitializeが呼ばれないみたいだ。set_trace_funcにすら反応しなかった。



['x*2' | 'x <- xs', 'x <= 10']

この実装のために、caller bindingが取得できなくて困っていたところに、るびまRailsの記事でRailsはset_trace_funcで実現していることを知った。というわけで、躊躇なくbinding_of_callerを利用。スレッドセーフではなくなりそうだけど、その辺は継続を使うことでどうにかしてるのかな。そのうち見てみよう。

それにしてもメタプログラミングは楽しいや。