Haskell ではリスト内包表記って使ったことないんだよなー

This entry was posted by on Saturday, 2 June, 2007

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 は利用経験あり)。まあ、そういうのがあると便利な問題とそうでない問題というのがあり、わたしはもっぱら後者を経験しているのかもしれないけれど、ほかの言語がこぞって取り入れるほど優れた記法かなあ……という気もするのでした。

Comments are closed.