Ruby における関数オブジェクトとブロック引数とは…?
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])