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を呼び出す
- Procを生成するには、
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と同じくクロージャであり、定義されたスコープで評価される
- メソッド
- オブジェクトに束縛され、オブジェクトのスコープで評価される
- オブジェクトのスコープから引き離し、他のオブジェクトに束縛することもできる。
感想
な、なんとかまとめてみた。。。
ちょっと、この章の理解が全章までと比べて弱い...
ブロックは、わかった気になってるけど、なんだろう。慣れてない
TODO: 時間を空けてもう一度読み直す