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価の関数しか扱えないのがちょっと難かなぁ。