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ならずっと綺麗に書けるだろう。むー。