0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

block、proc、lambdaの話

Posted at

今まで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を使う場合に、method1method2を実行する際は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なので、yieldblock.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について纏めました。
理解不足により、認識が間違っている点や不明点等あればご指摘下さい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?