Ruby 2.6 で追加される Proc#>> に Symbol も渡したいよねー内部で #to_proc も呼び出してほしいよねーと考えた時の覚書。
現在
Proc#>>には#callが定義されているオブジェクトを渡せる
なにをしたい
Proc#>>に#to_procが定義されているオブジェクトも渡したいmethod(:hoge) >> foo.to_procをmethod(:hoge) >> fooと書きたいProc#>>にSymbolも渡したい#to_procを Refinements で定義されている場合にも渡したい
class Array # to_proc を Refinements で定義したい… def to_proc proc { |*args| self.map { |it| it.to_proc.call(*args) } } end end User = Struct.new(:id, :name, :age) users = [ User.new(1, "Homu", 14), User.new(2, "Mami", 15), User.new(3, "Mado", 14), ] users.map &method(:pp) >> [:name, :age]
メモ
#call について
- Ruby では
#callが生えているオブジェクトは関数オブジェクトとして扱われるProcやMethodなど
meth(&block)のようにブロック引数で受け取ってblock.callのように呼び出す
#to_proc について
#to_procは『関数オブジェクト』を返すメソッド#to_procはブロック引数のシンタックスシュガーとして使われる&hogeの時にhoge.to_procが呼ばれる
#to_procが定義されているだけでは『関数オブジェクト』と呼べない- ブロック引数で渡したいと気に
#to_procを定義する
Proc#>> について
Proc#>>は関数オブジェクトを受け取る- 内部で受け取ったオブジェクトの
#callを呼び出している
- 内部で受け取ったオブジェクトの
- なので内部で
#to_procを呼び出すような挙動は一貫性がない#to_procが定義されているだけのオブジェクトは『関数オブジェクト』ではない
Proc#>>で#to_procが定義されているオブジェクトを受け取りたい場合はProc#>>(a)ではなくてProc#>>(&block)で受け取るべき- 結果的に
#to_procが定義されているオブジェクトを渡すことが出来る
- 結果的に
- しかし
method(:hoge).<< do ... endみたいに『ブロック構文』を使うような使い方は想定していない(と思うので)Proc#>>(&block)を定義するのはおかしい#to_procを呼び出したいだけのために&block引数を定義するのはおかしい
Proc#(a, &block)みたいに定義することで#callと#to_procの両方を受け取ることが出来る- ただし、
aと&blockの両方を渡した場合にどうするのか、という問題は残る
- ただし、
まとめ
任意の関数で、
- 関数オブジェクトを受け取りたい
- ブロック引数で受け取りたい
- ブロック引数で受け取るという前提で
meth(&block)渡しが出来る
- ブロック引数で受け取るという前提で
を切り分ける必要がある。
今回の Proc#>> は『ブロック引数』で受け取るのではなくて『関数オブジェクト』を受け取るので #to_proc を渡すのは難しそう
Proc#>> のように『関数オブジェクト』を期待するメソッドに対しては #call を定義したオブジェクトを渡すべき
まとめ2
Proc#>>にSymbolを渡したいのであれば- →
Symbol#callを定義すべき
- →
Proc#>>に#to_procが定義されているオブジェクトを渡したいのであれば- →
Proc#>>(&block)のようにブロック引数で受け取るべき
- →
#to_procを呼び出すシンタックスシュガーがほしい…- 例えば
~hogeがhoge.to_procのシンタックスシュガーであれば(~は仮 method(:foo) >> :hoge.to_procがmethod(:foo) >> ~:hogeとかけたりwhen :even?.to_procをwhen ~:even?とかけたりする
- 例えば
そもそも…
次のような構文はシンタックスエラーになる…
class X def << &block end end # syntax error, unexpected & X.new << &:hoge
備考
- 関数オブジェクト:
#callが定義されているオブジェクト - ブロッカブルオブジェクト:
#to_procが定義されているオブジェクト - 両対応する場合、どうするのがよいか
コード例
Proc#>> に渡したいのであれば以下のように #to_proc を定義するのではなくて
class Array def to_proc proc { |*args| self.map { |it| it.to_proc.call(*args) } } end end User = Struct.new(:id, :name, :age) users = [ User.new(1, "Homu", 14), User.new(2, "Mami", 15), User.new(3, "Mado", 14), ] users.map &method(:pp) >> [:name, :age]
以下のように #call を定義するべき
require "pp" class Array def call *args self.map { |it| it.to_proc.call(*args) } end end User = Struct.new(:id, :name, :age) users = [ User.new(1, "Homu", 14), User.new(2, "Mami", 15), User.new(3, "Mado", 14), ] users.map &(method(:pp) << [:name, :age])