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)だの、この手のUnixのUnixたるところを示す典型的フィルタコマンドは関数型言語のほうが綺麗に書けるんだろうな。Rubyだといまいちぱっとしない。むやみにProcにメソッド追加するのもアレだし、かといってlambda相当物はProc以外にないので苦労している。それでも、こんな風にソート処理を分解しておけば、あとから-f (--ignore-case)オプションを追加しようと思ったとしても、proc{|x| x.downcase}を掛けるだけだから拡張が簡単である。
一応、上のコードは、ソート関数を「要は全順序だろ」と思って係数体が{-1, 0, 1}のベクトル空間と見なすやりかただな。えっと比較対象の行に対する逆元をうまく定義できるとすれば、線形性は満たすよな。ただ、辞書式順序をcartesianと名付けたのはちょっと不正確か? 線形写像のカルテシアン積の、その{-1, 0, 1}への線形な写像だよな。
久々にcoerce規約を実装して楽しかった。でも、これHaskellならずっと綺麗に書けるだろう。むー。