String#succで提起されている問題はかなり面白い。これをどう捉えるかが、数学屋と計算機屋で分かれそうだ。
Ruby的にスマートそうな回答はwalf443さんが提出済み。succに限らない一般化バージョンもある。
でも、私の目には、モノが"succ"だけにこれは関数合成の問題に見える。"str".succ.succが(succ ∘ succ)("str")に見えるのね。今『圏論の基礎』に再挑戦してるからsucc succ "str"と書こうか。
数学屋-計算機屋と書いたけれど、これは関数型言語系と手続き型言語系で分かれるといった方が正しいかもしれない。このあたり、関数型言語なら多変数/多価関数も含めてスマートに書けるんだろう。Haskellなんかsuccはまんまsucc (succ "str")だもんね。
そういう視点からこの問題を捉えると、実際この手のメソッドチェーンはレシーバーを出発点とした射の合成な訳だよね。ここで、ActiveSupportに入ったSymbol#to_procを思い出す(くまくま参照)。あんな感じで、シンボルを元に射の合成を表現できたらいいなぁ、と。
実装
で、書いてみました。
class Symbol
unless instance_methods.include?("to_proc")
def to_proc
Proc.new{|obj, *args| obj.__send__(self, *args)}
end
end
def * rhs
MethodComposition.new([self.to_proc, rhs.to_proc])
end
def ** num
MethodComposition.new(Array.new(num, self.to_proc))
end
end
class MethodComposition
def initialize(elements = [])
@chain = elements
end
def * rhs
MethodComposition.new(@chain.dup.push(rhs.to_proc))
end
def ** num
MethodComposition.new(@chain * num)
end
def to_proc
Proc.new{|receiver|
@chain.inject(receiver) {|obj, proc| proc.call(obj) }
}
end
end
class Object
def callcomp(proc = nil)
unless proc
self
else
proc.to_proc.call self
end
end
end
使いかた
例えば、
"b8".callcomp(:succ ** 5 * :succ * :succ ** 3) # => "c7"
1.callcomp(:succ ** 3 * :to_s) # => "4"
[64, 65, 66].map(&(:succ * :chr * :to_sym)) # => [:A, :B, :C]
感想
- 効率は無視してProcオブジェクト化して格納
- 関数合成だから、演算子は乗算と冪乗算を借りた
- あと、Symbolに乗算を定義する意味は普通無さそうなので衝突しなそう
- 名前が
Object#callcompなのはcallccからの連想だけれど、もっとまともな名前はないものかと自分でも思ってる - というか、
succを数回程度ならかえって複雑にしてるだけ - でも、mapとかに適用すると少しだけありがた味が出てくるかも
- 1変数(というか
self)で1価の関数しか扱えないのがちょっと難かなぁ。
コメント