[-1, -2, -3].map(&:succ >> :to_s)
という関数型を意識した記述
&
を使いブロック渡しで任意のオブジェクトを渡す仕組みがあります。
カラクリについては https://qiita.com/hamajyotan/items/f2a96bbb1ccc60a06053 に記述。
[-1, -2, -3].map(&:succ) #=> [0, -1, -2]
上記記述に対して、
Proc#>>
と同じ要領で succ
を実施した結果に更に to_s
を実施できたら嬉しいですね。
けど、できません。。
この記事はこれをできるようにするための実装の話です。
[-1, -2, -3].map(&:succ >> :to_s) #=> NoMethodError: undefined method `>>' for :succ:Symbol
Proc#>> および Proc#<<
Ruby 2.6 から、 Proc#>> および Proc#<< が追加されました。いわゆる関数合成と言われる仕組みです。
f1 = -> x { x.succ }
f2 = -> x { x.to_s }
f3 = f1 >> f2
f3.call(-3) # f1 の実行結果を f2 の引数として渡す。 f2.call(f1.call(-3))
#=> "-2"
f4 = f1 << f2
f4.call(-3) # >> とは逆順に合成される。 f1.call(f2.call(-3))
#=> "-4"
Symbol#>> と Symbol#<< があったら嬉しい
自分自身を to_proc
により Proc オブジェクトに変換、そこに更に >>
で合成すればよさそうです。
# >> および << メソッドを生やす
module SymbolComposeToLeftAndRight
def >>(g)
to_proc >> g
end
def <<(g)
to_proc << g
end
end
Symbol.prepend(SymbolComposeToLeftAndRight)
# どうだ!?。。。残念
[-1, -2, -3].map(&:succ >> :to_s) #=> NoMethodError: undefined method `call' for :to_s:Symbol
どうやら Proc#>>
の引数に対して call
を要求しているようです。
Proc の >> および << の引数側も to_proc するようになれば嬉しい
Proc#>>
および Proc#<<
の引数は Proc オブジェクトであることが期待されています。
もう少し正確に言うと、 call
に反応するオブジェクトであることが期待されています。
class Callable
def call(x)
x + 5
end
end
proc1 = proc { |x| x + 1 }
proc2 = proc1 << Callable.new
# 引数に 1加算され、更にその結果に 5加算。
proc2.call(3) #=> 9
ここが拡張されて、引数自体に to_proc
がかかってから既存の処理となるようにならないでしょうか?
言い換えると、 .call
を期待するオブジェクト改め、 .to_proc.call
を期待するオブジェクトと言っても良いです。
module ProcComposeToLeftAndRightWithToProc
def >>(g)
super(g.to_proc) #=> 引数オブジェクトを to_proc してから既存の処理
end
def <<(g)
super(g.to_proc)
end
end
Proc.prepend(ProcComposeToLeftAndRightWithToProc)
これで上記エラーも回避できるはず。
実装した (まとめ)
module ProcComposeToLeftAndRightWithToProc
def >>(g)
super(g.to_proc)
end
def <<(g)
super(g.to_proc)
end
end
Proc.prepend(ProcComposeToLeftAndRightWithToProc)
module SymbolComposeToLeftAndRight
def >>(g)
to_proc >> g
end
def <<(g)
to_proc << g
end
end
Symbol.prepend(SymbolComposeToLeftAndRight)
# succ してから to_s
[-1, -2, -3].map(&:succ >> :to_s) #=> ["0", "-1", "-2"]
# succ してから to_s, さらに length
[-1, -2, -3].map(&:succ >> :to_s >> :length) #=> [1, 2, 2]
# 逆側の合成。 to_s してから succ
[-1, -2, -3].map(&:succ << :to_s) #=> ["-2", "-3", "-4"]
関数型っぽい!
既存 2.6 とも互換はあるし、 Ruby の自体がこうなってたら嬉しい人は少なくないんじゃないかと思うけどどうでしょう?