sortコマンドと無名関数の合成

sort コマンドの実装(その2 を読んで、単なる「引数に対する操作を表すオブジェクト」のためにクラスを定義する意味が分かんね、と思っていろいろ書いてみたらこうなった。

require 'optparse'

module Ordering
  def cartesian(rhs)
    proc {|(a0,*ar), (b0,*br)|
      result = self.call(a0, b0)
      result == 0 ? rhs.call(ar, br) : result
    }.extend(Ordering)
  end
  def composite(rhs)
    proc {|a,b| self.call(rhs.call(a), rhs.call(b)) }.extend(Ordering)
  end
  def scalar_prod(rhs)
    case rhs
    when -1 then -self
    when  0 then NULL
    when  1 then self
    else raise ArgumentError, "must be -1, 0, or 1"
    end
  end
  def -@
    proc {|a,b| -self.call(a,b)}.extend(Ordering)
  end
  def *(rhs)
    case 
    when rhs.respond_to?(:call)
      case rhs.arity
      when 1 then self.composite(rhs)
      when 2 then self.cartesian(rhs)
      end
    when -1, 0, 1 
      scalar_prod(rhs)
    else
      raise TypeError
    end
  end
  def coerce(lhs)
    case lhs
    when -1, 0, 1 then [self, lhs]
    else super
    end
  end

  DEFAULT = proc{|a,b| a <=> b}.extend(Ordering).freeze
  ennumeric = proc{|x| /^(\d*|\s+\d+)(.*)$/ =~ x; [$1.to_i, $2]}
  NUMERIC = (DEFAULT*DEFAULT*ennumeric).freeze
  NULL = proc{|a,b| 0}.extend(Ordering).freeze
end

sort = Ordering::DEFAULT
reverse = 1

opt = OptionParser.new
opt.on("-n", "--numeric-sort") {|v| sort = Ordering::NUMERIC }
opt.on("-r", "--reverse") {|v| reverse = -1}
opt.parse!(ARGV)

ARGF.sort(&reverse*sort).each{|v| print v}

結局、sort(1)だのyes(1)だの、uniq(1)だの、この手のUnixUnixたるところを示す典型的フィルタコマンドは関数型言語のほうが綺麗に書けるんだろうな。Rubyだといまいちぱっとしない。むやみにProcにメソッド追加するのもアレだし、かといってlambda相当物はProc以外にないので苦労している。それでも、こんな風にソート処理を分解しておけば、あとから -f (--ignore-case) オプションを追加しようと思ったとしても、 proc{|x| x.downcase} を掛けるだけだから拡張が簡単である。

一応、上のコードは、ソート関数を「要は全順序だろ」と思って係数体が{-1, 0, 1}のベクトル空間と見なすやりかただな。えっと比較対象の行に対する逆元をうまく定義できるとすれば、線形性は満たすよな。ただ、辞書式順序をcartesianと名付けたのはちょっと不正確か? 線形写像のカルテシアン積の、その{-1, 0, 1}への線形な写像だよな。

久々にcoerce規約を実装して楽しかった。でも、これHaskellならずっと綺麗に書けるだろう。むー。