Rubyでリスト内包表記 を見て「そりゃないだろう」とちょっとしたコメントを付しました。
「そりゃないだろう」の配分は「正規表現かよ!」が4で「文字列かよ!」が6くらいかなあ。
後者から。言語内言語を文字列として保持させておいてあとで何らかの機構でもって評価させる、ってのは個人的には好きになれないんですね。 SQL の埋め込みとかも本当はわたしは好きではない(仕方ないが)。たとえば ActiveRecord みたいなアプローチがベストだとは思わないけれど、たとえば LINQ みたいな方法のがいいでしょうとか思ってしまうわけです。よく知らないけど、 LINQ。
でもってもうひとつ気になるのは、正規表現だということですね。こういうのにはつい正規表現を使いたくなるものなんですが、結果的にあれこれ不思議なルールを追加しないと上手く行かなくなってしまうんじゃないかと思うんですね。それは上手くない。コメントではうっかり「Ripperとか」と口走ってしまいましたが、それはともかく何らかのパーサを使うのがいいと思います。
もっとも、上手に scan を駆使すれば簡単にパースできるような気もするなあ、などとコメントしたあとで思ったのですが、ともかく正規表現でいきなり区切るのは感心しないなあと。
ただケチをつけるばっかりというのもナンですから、と思って「自分だったらどう実装するか」というのを現実逃避がてらあれこれ考えてました。で、最終的に思いついた設計は次みたいな感じ。
class ListComprehension
BindVar = Struct.new("BindVar", :symb, :arr)
Conditional = Struct.new("Conditional", :symb, :name, :args)
def initialize(list)
@result_value = list[0]
@list = list[1..-1]
end
def calculate
res = []
calculate_inner(res, {}, 0)
res
end
def evaluate(b, v)
case v
when Symbol
b.fetch(v){v}
when Conditional
receiver = b.fetch(v.symb){raise NameError}
args = v.args.map{|e| evaluate(b, e)}
receiver.__send__(v.name, *args)
when Array
v.map{|e| evaluate(b, e)}
else
v
end
end
def calculate_inner(res, b, i)
if i < @list.size then
typ, symb, *arr = @list[i]
case @list[i]
when BindVar
arr = @list[i].arr
symb = @list[i].symb
arr.each do |v|
b[symb] = v
calculate_inner(res, b, i+1)
end
b.delete(symb)
when Conditional
calculate_inner(res, b, i+1) if evaluate(b, @list[i])
end
else
res.push(evaluate(b, @result_value))
end
end
private :calculate_inner
end
class Symbol
def <<(arr)
ListComprehension::BindVar.new(self, arr)
end
def method_missing(name, *args)
ListComprehension::Conditional.new(self, name, args)
end
end
def lc(arr)
ListComprehension.new(arr).calculate
end
「うげー」という声が聞こえてきそうですが。しかもこんななりのわりに激しくショボイ。実行例はこんな感じで。
irb(main):001:0> require 'list_comprehension' => true irb(main):002:0> lc [[:x, :y], :x << (1..5), :y << (1..5)] => [[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5]] irb(main):003:0> lc [:x, :x << (1..5), :y << [2, 3], :x <= :y] => [1, 1, 2, 2, 3] irb(main):004:0> class Fixnum; def even?() self % 2 == 0; end; end => nil irb(main):005:0> lc [[:x, :y, :x+:y], :x << (1..5), :x.even?, :y << (1..5)] => [[2, 1, 3], [2, 2, 4], [2, 3, 5], [2, 4, 6], [2, 5, 7], [4, 1, 5], [4, 2, 6], [4, 3, 7], [4, 4, 8], [4, 5, 9]]
なぜ even? メソッドが必要なのかというと、 「:x % 2」は ListComprehension::Conditional オブジェクトであり、そこでさらに0と比較しようとするとふつうに比較されちゃうんですね。本気で頑張るならその辺も含めてキッチリと再定義することになるのかな。もう飽きたのでやりませんが。というか、さすがに Symbol#method_missing は影響が強すぎるだろうな。あくまでも「お遊び」の範疇ってことで許してね。
しかし、リスト内包表記ってそれなりの規模のプログラムでは書いたことがない(リストモナドというか ListT は利用経験あり)。まあ、そういうのがあると便利な問題とそうでない問題というのがあり、わたしはもっぱら後者を経験しているのかもしれないけれど、ほかの言語がこぞって取り入れるほど優れた記法かなあ……という気もするのでした。