事の始まり
@kasei_san [1,2,3].each &method(:puts)的な…
— ほしの hoshinotsuyoshi (@hoppiestar) 2015, 1月 20
やってみる
irb(main):001:0> [1,2,3].each(&method(:puts))
1
2
3
おお、うごいた!!
でもこれ何やってるの?
まずは & 演算子について
色んな意味があるけど、今回は以下の用法で使っている
xxx(&b)
Proc オブジェクトをブロックとして使う。メソッド呼び出し(super・ブロック付き・yield)/ブロック付きメソッド呼び出し を参照。
→ &演算子を使うと、procオブジェクトをブロック引数の代わりにメソッドに渡せる
次にProcオブジェクトについて
ブロックの部分だけを先に定義して変数に保存しておき、後からブロック付きメソッドに渡すことも出来ます。 それを実現するのが手続きオブジェクト(Proc)です。 それをブロックとして渡すにはブロック付きメソッドの最後の引数として `&' で修飾した手続きオブジェクトを渡します。
procって何の略称?
procedureの略
プロシージャ (procedure)とは、プログラミングにおいて複数の処理を一つにまとめたものをいう。手続きとするのが定訳である。一連の処理を意味を持った一まとまりにすることで、再利用性が高まり、プログラム中に繰り返して現れる処理を一ヶ所で記述でき、プログラムの管理を容易にする。
→ procは、ブロック引数に渡す処理を先に記述する仕組みで、これにより再利用性が高まる
Procのサンプル
p = Proc.new {|s| s.to_s}
[:aaa, :bbb, :ccc].map(&p) #=> ["aaa", "bbb", "ccc"]
これ、こういう書き方できるよね?
[:aaa, :bbb, :ccc].map(&:to_s) #=> ["aaa", "bbb", "ccc"]
これだと、Procの代わりにシンボルを渡しているけど、どういうことなの?
to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます。デフォルトで Proc、Method オブジェ クトは共に to_proc メソッドを持ちます。to_proc はメソッド呼び出し時に実 行され、Proc オブジェクトを返すことが期待されます。
Symbol#to_proc
self に対応する Proc オブジェクトを返します。
生成される Proc オブジェクトを呼びだす(Proc#call)と、 Proc#callの第一引数をレシーバとして、 self という名前のメソッドを 残りの引数を渡して呼びだします。
やってみる
:to_s.to_proc.call(:abc) # => "abc" (:abc.to_s が呼ばれる)
[:a, :b, :c].map(&:to_s.to_proc) #=> ["a", "b", "c"]
Procオブジェクトのまとめ
- ブロック引数には、&演算子を使ってブロックの代わりにProcオブジェクトを渡すことができる
- ブロック引数には、Procオブジェクト以外にも to_proc メソッドを持つオブジェクトを渡すこともできる
- Symbol#to_procは、selfにあるシンボルと同名のメソッドを呼び出すProcオブジェクトを生成する
最後にObject#method について
オブジェクトのメソッド name をオブジェクト化した Method オブジェクトを返します。
メソッドオブジェクト
Object#method によりオブジェクト化され たメソッドオブジェクトのクラスです。
メソッドの実体(名前でなく)とレシーバの組を封入します
やってみる
class Counter
attr_reader :count
def initialize
@count = 0
end
def inclement
@count = +1
end
end
foo = Counter.new
m = foo.method(:count)
p m.call #=> 0
foo.inclement
p m.call #=> 1
Method オブジェクトも to_proc メソッドを持っているのでブロックに渡すことが可能
class Counter
attr_reader :count
def initialize
@count = 0
end
def add(cnt)
@count += cnt
end
end
cnt = Counter.new
m = cnt.method(:add)
m.call(10)
p cnt.count #=> 10
10.times(&m)
p cnt.count #=> 55
Object#method の使い道って?
例えば、何かしらの処理の結果を受け取りたい時とか
普通はダックタイピング的にメソッドを定義するけど、メソッドオブジェクトを渡すようにすれば、メソッド名を気にする必要がなくなる
# 何かしらの処理
#
def compute(recelver)
# 結果を引き渡す
result = "結果"
recelver.after_compute(result)
end
class Foo
# この場合、結果を受け取るメソッド名は固定
def after_compute(result)
p result
end
end
f = Foo.new
compute(f)
# 何かしらの処理
#
def compute(recelver)
# 結果を引き渡す
result = "結果"
recelver.call(result)
end
class Foo
def recelver(result)
p result
end
end
f = Foo.new
# Object#method を使えば結果を受け取るメソッドを自由に指定できる
compute(f.method(:recelver)) #=> "結果"
メソッドオブジェクトまとめ
- Object#method でオブジェクトにあるメソッドをオブジェクトにできる
- メソッドオブジェクトには、to_proc があるので、&演算子でブロック引数に渡すことができる
- メソッドをブロック引数に渡すことができる!
[1,2,3].each(&method(:puts)) について
-
method(:puts)
は、Kernel#puts のメソッドオブジェクト化 -
each(&method(:puts))
で、each のブロック引数に、Kernel#puts のメソッドオブジェクトを渡している
なので、
[1,2,3].each(&method(:puts))
は、
[1,2,3].each {|i| puts i }
と等しい!
まとめ
ブロック引数にProcオブジェクトを渡したい時は、&演算子を使う
func = Proc.new{|i| i*i}
[1,2,3].map(&func) #=> [1,4,9]
配列の要素が持つメソッドを呼ぶ場合 & 演算子と、to_proc メソッドの組み合わせを使う
[1,2,3].map(&:to_s) #=> ["1","2","3"]
他のオブジェクトが持つメソッドを使いたい場合、Object#method を使う
[1,2,3].each(&method(:puts)) #=> "1" "2" "3"
参考
以上、@hoshino さんありがとうございました!!