LoginSignup
1
0

More than 1 year has passed since last update.

Rubyのブロックを解説してみた

Posted at

メタプログラミングRubyの4章を読んでアウトプットがてら解説記事を書いてみようとおもいます。

まずは基本から

まずはブロックとは何かという基本的なものを確認しましょう。
do ~ end{}で定義されたもので、メソッドが呼び出されたときのみブロックを定義できます。
そして、ブロックはどこでも存在できるコードではないんです。ローカル変数やselfといったものが必要になるんです。
これらはオブジェクトと関連性をもったもので束縛と呼ばれることもあります。
実際にコードを見ていきましょう

def ex_method
  yield
end

var = 1

ex_method do
  var += 1
  local_var = 0
end

var #=> 2
local_var #=> NameError (undefined local variable or method `local_var' for main:Object)

上記のコードのように、ブロック内で新しく束縛を定義したとき、ブロックが終了した時点でその束縛は消えてしまう。
だからこそNameErrorが発生している。
これらを深く学んでいくためには、スコープを知っていく必要があります。

スコープ

どんな風にスコープが変化するかを実際に見ていきましょう

var1 = 1

class Example
  var2 = 2
  local_variables # => [:var2]
  def ex_method
    var3 = 3
    local_variables
  end
  local_variables # => [:var2]
end

obj = Example.new
obj.ex_method #=> [:var3]
obj.ex_method #=> [:var3]
local_variables

新しいスコープに入ると、それまでの束縛は新しい束縛に取って代わられるんです。
今回の例でいえば、Exampleクラスに入るとvar1はスコープ外となるんです。
そしてExampleクラスのクラス定義が終わると、トップレベルのスコープ、つまりvar1に戻るんです。
Exampleクラスのオブジェクトを作成して、ex_methodを参照しているとき、どんなことが起きているんでしょうか?

1回目にex_methodを参照するときは、新たにスコープをオープンしてローカル変数であるvar3が呼び出されている。
そしてメソッドを終了させてExampleクラスのスコープに戻り、メソッドのスコープは終了します。

2回目にex_methodを参照すると、別の新たなスコープをオープンして、新たなローカル変数であるvar3が呼び出されています。
見た目は同じように見えますが、object_idを調べてみると違うことが分かるかとおもいます。

このように、スコープを変えると、それまでの束縛と新たな束縛が置き換わるんです。これが重要なポイントです。

フラットスコープ

スコープが変わるとそれまでの束縛と新たな束縛が置き換わることが分かったとおもいます。
では、以下のような場合どうしたらよいでしょうか?

var1 = "OK"

class Example
  # ここにvar1 = "OK"を表示したい

  def ex_method
    # ここにvar1 = "OK"を表示したい
  end
end

classをスコープゲートではない何かに置き換えればいいんです。メソッド呼び出しをするといいんです。
class.newにブロックを渡すとクラスにインスタンスメソッドを定義できるんです。

var1 = "OK"

Example = Class.new do
  # ここにvar1 = "OK"を表示したい
  puts "表示:#{var1}" #=>表示:OK

  def ex_method
    # ここにvar1 = "OK"を表示したい
  end
end

つぎにdef内にvar1を表示させるにはどうしたらいいでしょうか?define_methodを使ういいです。

var1 = "OK"

Example = Class.new do
  # ここにvar1 = "OK"を表示したい
  puts "表示:#{var1}" #=>表示:OK

  define_method :ex_method do
  puts "ここに表示されるのは#{var1}です"
  end
end
puts Example.new.ex_method
# =>ここに表示されるのはOKです

このようにすることで、他のスコープの変数を参照できるようになるんです。
これをフラットスコープといいます。スコープを参照できるできないの差を無くす、つまりフラットにするという意味ですね。
define_method,Class.new,Module.newを使うことで、あるスコープは束縛を参照できる、できないを自由自在に扱えるようになるんですね。すごい(笑)

instance_eval

もう一個コードと束縛を自由自在に操られるメソッドがあります。それがinstance_evalというものです。
実際にコードを見ていきましょう。

class Example
  def initialize
    @v = 'hoge'
  end
end

obj = Example.new

obj.instance_eval do
  self => #<Example:0x000001c897df4678 @v="hoge">
  @v => #"hoge"
end

instance_evalに渡したブロックは、レシーバがselfになってから評価されるので、レシーバのあらゆる値(privateメソッドやインスタンス変数など)を参照できるんです。
このようにinstance_evalに渡したブロックのことをコンテキスト探査機と呼ばれます。個人的にはレシーバのあらゆる値が参照できてしまうのは、いいような悪いような気がします。

Procとlambdaについて

procについてこちらの記事に以前記事にしたのでこちらを参考にしてください。めちゃ簡単にいうとブロックをオブジェクト化したものです。
Procとlambdaの違いはめちゃめちゃアイマイなんです。この2つの違いを深堀は今回はしませんが、押さえておきたい2つの違いがあります。

return

1つ目の違いは、returnキーワードに関してです。
lambdaはreturnした後にメソッドに戻り、メソッドを最後まで実行するのに対して、Procはreturn後にメソッド自体を抜けてしまいます。
言葉で言われてもわからないので実際にコードを見てみましょう。

# lambdaの場合
def lambda_method
  lambda1 = lambda { return p "lambda"}
  lambda1.call
  p "lambda method"
end

lambda_method #=> "lambda"
              #=> "lambda method"


# Procの場合
def proc_method
  proc = Proc.new { return p "proc"}
  proc.call
  p "proc method"
end

proc_method #=> "proc"

lambdaの場合は、単にlambdaから戻っているため、メソッド内の処理をすべて実行してくれます。
それにたいして、Procのときは、returnした後メソッドから抜けてしまうため、proc methodという文言が出力されないんです。

引数の違い

2つ目の違いは引数のチェック方法が違います。実際にコードを見ていきましょう。

# Procの場合
proc1 = proc { |arg| p arg }
proc1.call( 'proc', 'lambda') #=> "proc"


# lambdaの場合
lamd = lambda { |arg| p arg }
lamd.call('lambda') #=> "lambda"
lamd.call('lambda', 'proc') #=>  wrong number of arguments (given 2, expected 1) (ArgumentError)

Procの場合は、指定している引数より多い場合、多い分は無視してくれるんですね。
それにたいして、lambdaの場合は、指定している引数より多い場合、ArgumentErrorが出るんです。
個人的には、lambdaをつかうのがいいのかなとおもいます。

DSLについてまとめようと思ったのですが、ちゃんと理解できなかったのでまたの機会にしようかとおもいます。
以上です。何か間違いがございましたら、ご教示いただけますと幸いです。
【参考資料】

1
0
1

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
1
0