DesignAssembler

備忘録に近い

Rubyのブロック

メタプログラミングRubyを読んでいます。 第3章の記事です。

ブロック

「呼び出し可能オブジェクト」の1つで、スコープを制御します ブロックはオブジェクト指向ではなく関数型プログラミング言語の流れを汲んでいます

やること

  • ブロックの基本
  • スコープの概要とブロックをクロージャとして使って変数のスコープを越える方法
  • instance_eval()にブロックを渡してスコープを操作する方法
  • ブロックをProcやlambdaなどの呼び出し可能オブジェクトに変換してあとで呼び出す方法

ブロックの基本

ブロックが1行の時は{ }、複数行の時はdo...endを使いましょう

github.com

ブロックはメソッドに渡され、yieldキーワードを使ってそのままコールバックされます。

def block_callback
  return yield
end

> block_callback {"this is block."}
 => "this is block." 
 > block_callback 
LocalJumpError: no block given (yield)

クロージャ

クロージャとは、Rubyが文脈によって変数のブロックを読み取ってくれる機能です。

def clojure_practice
  x = "x in method"
  yield("this is y")
end

> x = "this is x"
> clojure_practice{|y| puts x; puts y}
this is x
this is y
 => nil 

ここでxがメソッド内のものではなくブロックが定義されたときのものを見ています。これがクロージャです。

トップレベルのインスタンス変数

グローバル変数よりもmainオブジェクト(トップレベル)のインスタンス変数を使う方がいいです。

@top_var = "トップレベルの変数です"
def practice_method
  @top_var
end

> practice_method
 => "トップレベルの変数です"

スコープゲート

以下の3つのキーワードでスコープが入れ替えられます。これをスコープゲートといいます。

  • class
  • module
  • def

フラットスコープ

スコープをぶち抜く事を「入れ子構造のレキシカルスコープ」や「スコープのフラット化」といいます。

classやdefがあるとスコープが外れてしまうので、class、def以外でクラスとメソッドを定義します。

#スコープゲートを越えられない
var = "スコープ確認用"
class ScopePractice
  puts var
  def scope_method
    puts var
  end
end

NameError: undefined local variable or method `var' for ScopePractice:Class
  from (irb):3:in `<class:ScopePractice>'
    from (irb):2


#スコープゲートを越える
var = "スコープ確認用"
ScopePractice = Class.new do
  puts var + "クラス内"
  define_method(:scope_method) do
    puts var + "メソッド内"
  end
end

スコープ確認用クラス内
 => ScopePractice 
> ScopePractice.new.scope_method
スコープ確認用メソッド内

上だとスコープ外れてvarが見えませんが、下だとスコープがフラット化されたvarが見えるようになりました。

instance_eval()

インスタンス変数にアクセスするにはinstance_eval()を使います。

class ContextSearcher
  def initialize
    @var = "sample"
  end
end

> ContextSearcher.new.instance_eval{@var}
 => "sample"
> v = "test"
> ContextSearcher.new.instance_eval{@var = v}
 => "test"

このinstance_evalに渡したブロックをコンテキスト探査機と呼びます。

ここでは@varと@var = vが該当します。

呼び出し可能オブジェクト

Procオブジェクト

今まで書いてきたブロックはオブジェクトではありません。 ですが、ブロックを保管して後から実行したいときにオブジェクト化しておくと便利です。 ここで Procがでてきます。

def proc_method
  proc = Proc.new{puts "Procオブジェクトからの呼び出し"}
  proc.call
end

 > proc_method
Procオブジェクトからの呼び出し

この技術を遅延評価と呼びます。

また、ブロックをProcオブジェクトに変換するカーネルメソッドにlambda()とproc()があります。基本的に3つのうち好きなものを使えばいいようです。

Proc.newとlambda()の大きな違い

Proc.new()とlambda()にはいくつか違いがあって、その中でも最も影響が大きいのがreturnについてです。

  • Proc.newのreturnはスコープのreturnキーワードです。後続の処理は実行されません。(例えば、メソッド内で定義されたならそのメソッドのreturnキーワードになります。)
  • lambda()のreturnはそのlambda文を抜けるだけです。

Rubyistの多くはProcの機能が必要でない限りlambdaを選ぶみたいです。

lambda()とproc()の違いに関してはこちらに詳しく載っています。

qiita.com

ラムダ式(アロー演算子) Ruby1.9から導入されたlambdaを定義する記法です。以下の二つは同じ事を示します。

l -> {|x| x + 1}
l = lambda{|x| x + 1}

以下に詳しく載っています

www.atmarkit.co.jp

参考

maeshima.hateblo.jp

qiita.com

qiita.com

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版