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

## 4章 水曜日: ブロック

## はじめに

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

## ブロックの基本

- ブロックを定義できるのは、メソッドを呼び出す時だけ。

```ruby
def a_method(a,b)
	a + yield(a,b)
end

a_method(1,2){ |x, y|(x + y) *3 }
#=> 10
```

- メソッド内部では、Kernel#block_given?メソッドを使ってブロックの有無を確認できる

```ruby
def a_method
	return yield if block_given
	'ブロックがありません'
end

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

## ブロックはクロージャ

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

```ruby
def my_method
	x = "Goodbye"
	yield("cruel")
end

x = "Hello"
my_method{ |y| "#{x}, #{y} world" } #=> "Hello, cruel world"
```

- ブロックの中で新しい束縛を定義することもできる
  - ブロックが終了した時点で消えてしまう

```ruby
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といったキーワードで印が付けられている

```ruby
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を使う

```ruby
my_var = "成功"

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

### defの場合

- define_methodを使う

```ruby
my_var = "成功"

MyClass = Class.new do
	puts "クラス定義の中は #{my_var}!"
	
	define_method :my_method do
		"メソッド定義の中も #{my_var}!"
	end
end
```

### フラットスコープ

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

### スコープの共有

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

```ruby
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に渡したブロックのことを **コンテキスト探査機**と呼ぶ

```ruby
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を呼び出す

```ruby
inc = Proc.new{ |x| x + 1 }
inc.call(2) #=> 3
```

- Procを生成する方法
  - lambda / proc

```ruby
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オブジェクトとして取得できる

```ruby
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: 時間を空けてもう一度読み直す