0
1

More than 3 years have passed since last update.

読書ログ『メタプログラミング Ruby 第2版』4章

Posted at

4章 水曜日: ブロック

はじめに

  • ブロックとは、スコープを制御するのに強力なツール
  • ブロックは「呼び出し可能オブジェクト」大家族の一員
  • ブロックの家族はオブジェクト指向とは異なる、「関数型プログラミング 言語」の流れ

ブロックの基本

  • ブロックを定義できるのは、メソッドを呼び出す時だけ。
def a_method(a,b)
    a + yield(a,b)
end

a_method(1,2){ |x, y|(x + y) *3 }
#=> 10
  • メソッド内部では、Kernel#block_given?メソッドを使ってブロックの有無を確認できる
def a_method
    return yield if block_given
    'ブロックがありません'
end

a_method #=> "ブロックがありません
a_method { "ブロックがあるよ!" } #=> "ブロックがあるよ!"

ブロックはクロージャ

  • ブロックのコードを実行するには、環境(ローカル変数 / インスタンス変数 / self)が必要
    • これらオブジェクトに紐づけられた名前のことで、束縛と呼ばれる
  • ブロックには、コードと束縛の集まりの両方が含まれる
  • ブロックを定義すると、その時点でその場所にある束縛を取得する。
  • ブロックをメソッドに渡したときは、その束縛も一緒に連れて行く
def my_method
    x = "Goodbye"
    yield("cruel")
end

x = "Hello"
my_method{ |y| "#{x}, #{y} world" } #=> "Hello, cruel world"
  • ブロックの中で新しい束縛を定義することもできる
    • ブロックが終了した時点で消えてしまう
def just_yield
    yield
end

top_level_variable = 1

just_yield do
    top_level_variable += 1
    local_to_block = 1
end

top_level_variable #=> 2
local_to_block #=> Error !
  • ブロックがローカル束縛を包み込んで一緒に連れて行くので、ブロックはクロージャ

スコープ

  • プログラムがスコープを変えると、新しい束縛と置き換えられる
    • 一般的にはスコープが変わると、束縛はスコープを抜ける

スコープゲート

  • プログラムがスコープを切り替えて、新しいスコープをオープンする場所
    • クラス定義
    • モジュール定義
    • メソッド
  • プログラムがクラスやモジュールの定義、あるいはメソッドに出入りすると、スコープが変化する
    • 境界線は、class, module, defといったキーワードで印が付けられている
v1 = 1
class MyClass #スコープゲート: classの入り口
    v2 = 2
    local_variables             #=> ["v2"]
    def my_method               # スコープゲート: defの入り口
        v3 = 3
        local_variables
    end                                     # スコープゲート: defの出口
    local_variables             #=> ["v2"]
end

obj = MyClass.new
obj.my_method                       #=> [:v3]
local_variables                 #=> [:v1, :obj]
  • クラスやモジュールの定義のコードはすぐに実行されるが、メソッド定義のコードはメソッドを呼び出した時に実行される

スコープのフラット化

  • スコープゲートを通り抜けて、ローカル変数を持ち運ぶ

classの場合

  • Class.newを使う
my_var = "成功"

MyClass = Class.new do
    puts "クラス定義の中は#{my_var} !"

    def my_method
        ...
    end
end

defの場合

  • define_methodを使う
my_var = "成功"

MyClass = Class.new do
    puts "クラス定義の中は #{my_var}!"

    define_method :my_method do
        "メソッド定義の中も #{my_var}!"
    end
end

フラットスコープ

  • 2つのスコープを一緒の場所に押し込めて、変数を共有する魔術
    • 「スコープのフラット化」 / 「入れ子構造のレキシカル(静的)スコープ」とも

スコープの共有

  • 複数のメソッドで変数を共有したいが、その他からは見えないようにする
    • 全てのメソッドを同じフラットスコープに定義すればいい
    • ただ、実際には共有スコープはあまり使われない
def define_methods
    shared = 0

    Kernel.send :define_method, :counter do
        shared
    end

    Kernel.send :define_method, :inc do |x|
        shared += x
    end
end

define_methods

counter
inc(4)
counter
  • スコープゲート、フラットスコープ、共有スコープを組み合わせれば、スコープを自由にねじ曲げて、好きな場所から必要な変数を見に行けるようになる

クロージャのまとめ

スコープゲート

  • class / module / def

クロージャ

  • ブロックのこと
    • ブロックを定義すると、現在の環境にある束縛を包み込んで、持ち運ぶことができる

フラットスコープ

  • classは Class.new / moduleは Module.new / defは Module.define_methodと置き換えることができる

共有スコープ

  • 同じフラットスコープに複数のメソッドを定義してスコープゲートで守る

instance_eval

  • instance_evalに渡したブロックは、レシーバをselfにしてから評価されるので、レシーバのprivateメソッドや@vなどのインスタンス変数にもアクセスできる
    • Instance_evalに渡したブロックのことを コンテキスト探査機と呼ぶ
class MyClass
    def initialize
        @v = 1
    end
end

obj = MyClass.new

obj.instance_eval do
    self        #=> #<MyClass:0x3340dc @v=1>
    @v          #=> 1
end
  • コンテキスト探査機を使うと、カプセル化を破壊できる
    • irbからオブジェクトの中身を見たい時、instance_evalを使ってオブジェクトに入るのが近道
  • ブロックを評価するためだけにオブジェクトを生成するオブジェクトを、クリーンルームと呼ぶ
    • クリーンルームとして使うには、BasicObjectのインスタンスが最適

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

  • ブロックの使用は2つのプロセスに分けられる
    • コードを保管する
    • (yieldを使って)あとで呼び出す
  • Rubyでコードを保管できる場所
    • Procの中。これはブロックがオブジェクトになった物
    • Lambdaの中。これはProcの変形
    • メソッドの中。

Prcoオブジェクト

  • ブロックはオブジェクトではない
  • Procはブロックをオブジェクトにしたもの
    • Procを生成するには、Proc.newにブロックを渡す
    • オブジェクトになったブロックをあとで評価するには、Proc#callを呼び出す
inc = Proc.new{ |x| x + 1 }
inc.call(2) #=> 3
  • Procを生成する方法
    • lambda / proc
dec = lamda{|x| x-1 }
dec.class #=> Proc
dec.call(2) #=> 1

「Proc」対「lambda」

  • lambdaで作ったProcは、他のProcとは違い、lambdaと呼ばれる
  • Procとlambdaの違い
    • returunキーワードに関すること
    • 引数のチェックに関すること
  • 結局どっちを使えばいいの?
    • Procの機能が必要でない限り、最初にlambdaを選ぶ

Methodオブジェクト

  • Object#methodを呼び出すと、メソッドそのものをMethodオブジェクトとして取得できる
class MyClass
    def initialize(value)
        @x = value
    end

    def my_method
        @x
    end
end

object = MyClass.new(1)
m = object.method :my_method
m.call #=> 1
  • Methodオブジェクトはブロックやlambdaに似ている
    • lambdaは定義されたスコープで評価されるが、Methodは所属するオブジェクトのスコープで評価される

呼び出し可能オブジェクトのまとめ

  • ブロック
    • 定義されたスコープで評価される
  • Proc
    • Procクラスのオブジェクト。ブロックのように、定義されたスコープで評価される
  • lambda
    • Procクラスのオブジェクトだが、通常のProcとは微妙に異なる。ブロックやProcと同じくクロージャであり、定義されたスコープで評価される
  • メソッド
    • オブジェクトに束縛され、オブジェクトのスコープで評価される
    • オブジェクトのスコープから引き離し、他のオブジェクトに束縛することもできる。

感想

な、なんとかまとめてみた。。。

ちょっと、この章の理解が全章までと比べて弱い...
ブロックは、わかった気になってるけど、なんだろう。慣れてない:sweat:

TODO: 時間を空けてもう一度読み直す

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