最新

val it : α → α = fun

<<  2007/06  >>

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

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