今までRubyの世界にblock、proc、lambdaはいつても避けられない話と思うので、今回はも一回雑談させていただきます。
block
blockを含めたメソッドは下記のような書いていきます。
def method(*args, &block)
...
end
実験
引数みたいな&block
は実際に引数ではなく、声明のような物となります。
[20] pry(main)> def method(&block)
[20] pry(main)* end
=> :method
[21] pry(main)> method(1)
ArgumentError: wrong number of arguments (given 1, expected 0)
from (pry):21:in `method'
[22] pry(main)> method()
=> nil
[23] pry(main)> method() { p 'hello' }
=> nil
上記の結果によって、&block
は引数でないことを判明します。
blockの定義と呼び出す
[25] pry(main)> def method(arg)
[25] pry(main)* p arg
[25] pry(main)* end
:inspect
=> :method
[26] pry(main)> method(1) { p 2 }
1
=> 1
上記のdef method(arg)
で定義されたblockは呼ばれていないので、異常なしにp 1
の結果を出力しました。
[27] pry(main)> def method1
[27] pry(main)* yield
[27] pry(main)* end
:inspect
=> :method1
[28] pry(main)>
[29] pry(main)> def method2(&block)
[29] pry(main)* block.call
[29] pry(main)* end
:inspect
=> :method2
blockを呼び出せたいのなら、yield
や&block
を使えましょう。
[30] pry(main)> method1
LocalJumpError: no block given (yield)
from (pry):33:in `method1'
[31] pry(main)> method2
NoMethodError: undefined method `call' for nil:NilClass
from (pry):36:in `method2'
yield
や&block
を使う場合に、method1
やmethod2
を実行する際はblockを追加しなければいけないです。と言う分けて、rubyにはblock_given?
という判定メソッドがあります。
[32] pry(main)> def method1
[32] pry(main)* yield if block_given?
[32] pry(main)* end
:inspect
=> :method1
[33] pry(main)>
[34] pry(main)> def method2(&block)
[34] pry(main)* block.call if block_given?
[34] pry(main)* end
:inspect
=> :method2
[35] pry(main)> method1
:inspect
=> nil
[36] pry(main)> method2
:inspect
=> nil
上記のコードでblock_given?
の判定がfalseなので、yield
やblock.call
を実行せずにnilを返しました。
引数もチャックしない
[25] pry(main)> def method3(arg1, arg2)
[25] pry(main)* yield 10
[25] pry(main)* end
=> :method3
[26] pry(main)> method3(1, 2) do |arg1|
[26] pry(main)* p arg1
[26] pry(main)* end
10
=> 10
[27] pry(main)> method3(1, 2) do
[27] pry(main)* p 'nothing'
[27] pry(main)* end
"nothing"
=> "nothing"
blockは何だろう
blockの本体を探すために、メソッドの後ろに何らかのblockつけて見ましょう。
[39] pry(main)> def method3(&block)
[39] pry(main)* p block.class
[39] pry(main)* p block.inspect
[39] pry(main)* block.call
[39] pry(main)* end
:inspect
=> :method3
[40] pry(main)> method3 {}
Proc
"#<Proc:0x007f82028198f0@(pry):58>"
:inspect
=> nil
&block
した結果はProcのinstance objectと判明しました。
&
との関係
[41] pry(main)> int_array = [1, 2, 3]
=> [1, 2, 3]
[42] pry(main)> int_array.map(&:to_s)
=> ["1", "2", "3"]
&
というば、Ruby開発者は大体上記のコードを良く見えたと思いますが、map(&:to_s)
の書き方は簡潔ですし、格好良いな感じです。その魔力の源はまさに&
です。
class Symbol
def to_proc
proc { |obj, args| obj.send(self, *args) }
end
end
&
は:to_sのto_procを呼び出して、Procのinstance objectを返します。そして、&
はその「Procのinstance object」をblockとしてmap
につけます。
つまり、
int_array.map(&:to_s)
=>
int_array.map { |obj, args| obj.send(self, *args) }
となります。
真似て書こ
[53] pry(main)> class AddBy
[53] pry(main)* def initialize(num = 0)
[53] pry(main)* @num = num
[53] pry(main)* end
[53] pry(main)*
[53] pry(main)* def to_proc
[53] pry(main)* proc { |obj, args| obj.send(:+, @num, *args) }
[53] pry(main)* end
[53] pry(main)* end
:inspect
=> :to_proc
[54] pry(main)>
[55] pry(main)> add_by_3 = AddBy.new(3)
:inspect
=> #<AddBy:0x007f82521d3a18 ...>
[56] pry(main)> [1,2,3].map(&add_by_3)
=> [4, 5, 6]
block のかた
####doの型:
do |*args|
...
end
####{} の型:
{ |*args| ... }
####& の型
&:to_s
&add_by_3
...
但し、どの型でも独立存在することはできない。メソッドを先に発動しなければ、blockの型は使わないという認識です。そして、blockは変数として保存することもできないし、引数として他のメソッドに渡されることもできないです。そのために、procは登場します。
proc
変数として
[16] pry(main)> p = Proc.new(&:to_s)
=> #<Proc:0x00007f90c7cbb1c8(&:to_s)>
[17] pry(main)> p = proc(&:to_s)
=> #<Proc:0x00007f90c7cbb1c8(&:to_s)>
[18] pry(main)> p = proc {|obj| obj.to_s}
=> #<Proc:0x00007f90c7d0ae30@(pry):20>
[19] pry(main)> p = proc do |obj|
[19] pry(main)* obj.to_s
[19] pry(main)* end
=> #<Proc:0x00007f90c7d9b110@(pry):21>
引数として
[13] pry(main)> p = proc { puts 'I am a proc' }
=> #<Proc:0x00007f90c7b4ebc8@(pry):13>
[14] pry(main)> def method_with_proc(p)
[14] pry(main)* p.call
[14] pry(main)* end
=> :method_with_proc
[15] pry(main)> method_with_proc(p)
I am a proc
=> nil
実験
blockと同じく引数もチャックせず
[28] pry(main)> p = proc do |arg1, arg2|
[28] pry(main)* p arg1
[28] pry(main)* p arg2
[28] pry(main)* end
=> #<Proc:0x00007f90be320db0@(pry):49>
[29] pry(main)> p.call
nil
nil
=> nil
[30] pry(main)> p.call('name')
"name"
nil
=> nil
[31] pry(main)> p.call('name', 'is', 'colin')
"name"
"is"
=> "is"
blockとの関係
procとblockが別物でけど、procはblockをto_proc経由して実体化した物として理解して問題ないと思います。
そして、procのto_procはその自身を返す
[32] pry(main)> p = proc {}
=> #<Proc:0x00007f90c9867c00@(pry):56>
[33] pry(main)> p.equal? p.to_proc
=> true
lambda
procと全く同じものかな?
[43] pry(main)> p = proc {}
=> #<Proc:0x00007f90c9aed5d8@(pry):67>
[44] pry(main)> l = lambda {}
=> #<Proc:0x00007f90c9b26130@(pry):68 (lambda)>
[45] pry(main)> puts p.class
Proc
=> nil
[46] pry(main)> puts l.class
Proc
=> nil
実は、
[47] pry(main)> puts p.inspect
#<Proc:0x00007f90c9aed5d8@(pry):67>
=> nil
[48] pry(main)> puts l.inspect
#<Proc:0x00007f90c9b26130@(pry):68 (lambda)>
=> nil
末尾の(lambda)
はどういう事ですか???
実験
[54] pry(main)> def method_proc
[54] pry(main)* p = proc { return 'I am a proc' }
[54] pry(main)* p.call
[54] pry(main)* end
=> :method_proc
[55] pry(main)>
[56] pry(main)> def method_lambda
[56] pry(main)* l = lambda { return 'I am a lambda' }
[56] pry(main)* l.call
[56] pry(main)* end
=> :method_lambda
[57] pry(main)> method_proc
=> "I am a proc"
[58] pry(main)> method_lambda
=> "I am a lambda"
結局同じじゃないですか?
少々改修して、
[59] pry(main)> def method_proc
[59] pry(main)* p = proc { return 'I am a proc' }
[59] pry(main)* p.call
[59] pry(main)*
[59] pry(main)* 'I am a method'
[59] pry(main)* end
=> :method_proc
[60] pry(main)>
[61] pry(main)> def method_lambda
[61] pry(main)* l = lambda { return 'I am a lambda' }
[61] pry(main)* l.call
[61] pry(main)*
[61] pry(main)* 'I am a method'
[61] pry(main)* end
=> :method_lambda
[62] pry(main)> method_proc
=> "I am a proc"
[63] pry(main)> method_lambda
=> "I am a method"
return
を使ったら、大体procとlambdaの区別が出てくる。
lambdaは引数をチャックする
[64] pry(main)> l = lambda do |arg1, arg2|
[64] pry(main)* p arg1
[64] pry(main)* p arg2
[64] pry(main)* end
=> #<Proc:0x00007f90c7e62ee0@(pry):107 (lambda)>
[65] pry(main)> l.call('name')
ArgumentError: wrong number of arguments (given 1, expected 2)
from (pry):107:in `block in __pry__'
[66] pry(main)> l.call('name', 'is')
"name"
"is"
=> "is"
[67] pry(main)> l.call('name', 'is', 'colin')
ArgumentError: wrong number of arguments (given 3, expected 2)
from (pry):107:in `block in __pry__'
lambdaはメソッドみたいなものと認識れても問題ないと思います。
運用経験
そう考えたら、実際運用でblockとlambdaがあれば、十分ではないですか?procは蛇足な設計かな?
自分自身の経験によって、procはpowerfulと思います。
例えば、railsのcontrollerで、
def some_action(...)
...
return render :some_view, error: message1 if condition1
...
return render :some_view, error: message2 if condition2
...
return render :some_view, error: message3 if condition3
...
end
改修した後は、
def some_action(...)
some_proc = proc do |condition, message|
return render :some_view, error: message if condition
end
...
some_proc.call(condition1, message1)
...
some_proc.call(condition2, message2)
...
some_proc.call(condition3, message3)
...
end
まとめ
block、proc、lambdaについて纏めました。
理解不足により、認識が間違っている点や不明点等あればご指摘下さい。