【Ruby Advent Calendar 2017】Ruby で型チェックを実装してみよう【1日目】


[ブロク記事](http://secret-garden.hatenablog.com/entry/2017/12/01/000154)からの転載です。  

[Ruby Advent Calendar 2017](https://qiita.com/advent-calendar/2017/ruby) 1日目の記事になります。
Ruby 3 では静的型づけが入るとか言われていますが、今回は Ruby で動的に『型チェック』を行うコードを実装してみようと思います。

## 前書きという名の注意点

* 型チェックといいながら一般的な意味での型の話はしない
* あくまでも Ruby で『型チェックのようなもの』を実装するという話
* 型過激派の人は生暖かい目で見てください
* 今回は動的に型チェックを行うのでパフォーマンスに関しては考慮しない

## 今回実装する機能

* メソッドの引数に対して型チェック(精査)を行う
* 型によってメソッドの多重定義を行う

## ゴール

```ruby
class Person
	attr_accessor :name, :age

	def initialize
	end

	# name は String
	# age  は Integer
	# で受け取る initialize メソッドを定義
	define_typed_method(:initialize, name: String, age: Integer){
		# Hash のキーで変数を参照する
		self.name = name
		self.age  = age
	}

	define_typed_method(:set, name: String, age: Integer){
		self.name = name
		self.age  = age
	}

	def print
		puts "name:#{name} age:#{age}"
	end

	# フォーマットを渡して出力したり
	define_typed_method(:print, fmt: String){
		printf(fmt, name, age)
	}
end

homu = Person.new("homu", 14)

homu.print
# => name:homu age:14

homu.print("%s : %d\n")
# => homu : 14


mami = Person.new
mami.set("mami", 15)
mami.print
# => name:mami age:15
```

とりあえず、最終的には上記のようなコードが動作するようにしたいと思います。


## Ruby における型チェックとは

そもそも Ruby の型とは…型チェックとは…という話になるんですが、本記事では

* 型： `#===` が定義されているオブジェクト
* 型チェック： 型（`#===`） を使用して引数の値を精査する事

としたいと思います。
平たくいえば『`#===` を使って引数の値をチェックする』って感じですね。
なのでどちらかといえばバリデーションのような機構に近い形になると思います。

では、なぜ `#===` を使用するのかというと…。
例えばクラスオブジェクトでは `#===` は『引数がレシーバかそのサブクラスのインスタンスである場合』に真を返します。

```ruby
String === "homu" # => true
String === 42     # => false

Integer === 42    # => true
Integer === 3.14  # => false

Numeric === 42    # => true
Numeric === 3.14  # => true
```

上記のコードを見るとなんとなく型チェックっぽく見えますよね？
`#===` は本来 `when` で呼び出される事が想定されていますが、今回は

* `#===` が真を返せば OK
* `#===` が偽を返せば NG

という風にしてみたいと思います。
他にも `#===` を使用して型チェックを行う利点はあるのですが、それは後で記述します。


## 型チェックを行うメソッドを定義する

さて、では早速コードを書いていきましょう。
まずは以下のような感じで実装してみます。

* 型を渡してメソッドを定義する
* メソッドの呼び出し時に引数に対して型チェックを行う
  * 引数が定義した型に対して OK ならそのままメソッドを呼び出す
  * NG なら `super()` を呼び出す

```ruby
# クラスメソッドとして定義するので Module クラスを拡張
class Module
	# 型チェック付きメソッドを定義する
	# 第二引数には #=== で精査する値を渡す
	def define_typed_method name, *sig, &defmethod

		# 型リストと引数を #=== で比較する
		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		define_method(name){ |*args|
			# 型チェックを行い OK ならブロックを呼び出し、NG なら super() を呼ぶ
			valid === args ? defmethod.call(*args) : super(*args)
		}
	end
end
```

`define_method` のラッパーとして `define_typed_method` というメソッドを定義します。
このメソッドに対して『精査するする型』を渡し、内部で『メソッドの呼び出し時に型チェックを行うメソッド』を定義します。
ちなみに `super()` を呼び出すのは型チェックに失敗した場合にスーパークラスのメソッドにフォワードするためですね。

使い方は以下のような感じです。


```ruby
class X
	# func(Integer) のみ受け付けるメソッドを定義
	define_typed_method(:func, Integer){ |x|
		"func(Integer: #{x})"
	}

	# plus(Integer, String) のみ受け付けるメソッドを定義
	define_typed_method(:plus, Integer, String){ |a, b|
		a + b.to_i
	}
end

x = X.new
p x.func 42
# => "func(Integer: 42)"
p x.plus 1, "2"
# => 3

# Error: super: no superclass method `func' for #<X:0x0000000001376eb8> (NoMethodError)
p x.func "homu"
```

とりあえず、まずはこれを基準として<del>魔</del>改良していきたいと思います。


## 多重定義する

次は以下のように『型によって複数のメソッドを定義する』ことを考えてみましょう。

```ruby
class X
	# func(Integer) のみ受け付けるメソッドを定義
	define_typed_method(:func, Integer){ |x|
		"func(Integer: #{x})"
	}

	# func(String) のみ受け付けるメソッドも定義したい
	define_typed_method(:func, String){ |x|
		"func(String: #{x})"
	}
end

x = X.new
p x.func 42
# => "func(Integer: 42)"
p x.func "homu"
# => "func(String: "homu")"
```

いわゆる多重定義というやつですね。
これを実装するにあたって『Ruby で同名の複数のメソッドを保持する』必要があります。
今回は実装を簡単にしたかったので  mixin を利用したいと思います。

```ruby
class Module
	def define_typed_method name, *sig, &defmethod
		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		# 動的にメソッドを追加していく
		prepend Module.new {
			define_method(name){ |*args|
				# super() を介すことで以前に定義されたメソッドを呼び出す
				valid === args ? defmethod.call(*args) : super(*args)
			}
		}
	end
end


class X
	# func(Integer) のみ受け付けるメソッドを定義
	define_typed_method(:func, Integer){ |x|
		"func(Integer: #{x})"
	}

	# func(String) のみ受け付けるメソッドも定義したい
	define_typed_method(:func, String){ |x|
		"func(String: #{x})"
	}
end

x = X.new
p x.func 42
# => "func(Integer: 42)"
p x.func "homu"
# => "func(String: "homu")"
```

上記のように `prepend` を利用して継承リストに対して動的にメソッドを追加していきます。
これにより `super()` を利用して『以前に定義したメソッド』を呼び出すことが出来るようになります。
また、`prepend` なので『後から定義したメソッド』が優先して呼び出されます。

```ruby
class X
	define_typed_method(:func, String){ |x|
		"func(String: #{x})"
	}

	# こちらのほうが優先して呼び出される
	define_typed_method(:func, Object){ |x|
		"func(Object: #{x})"
	}
end

x = X.new
p x.func 42
# => "func(Object: 42)"

# Object === "homu" # => true
# なので Object で定義したメソッドのほうが優先して呼び出される
p x.func "homu"
# => "func(Object: "homu")"
```

内部で『定義したメソッドのリストを保持する』みたいな方が柔軟性は高いんですが、今回は実装をシンプルにしたかったのでこれで行きます。


## 定義するメソッド内の `self` をインスタンスオブジェクトにする

さて、次のように `define_typed_method` で定義したメソッド内で他のメソッドを呼び出すとエラーになってしまいます。

```ruby
class X
	def twice x
		x + x
	end

	define_typed_method(:func, String){ |x|
		# Error: undefined method `twice' for X:Class (NoMethodError)
		twice x.to_i
	}
end

x = X.new
p x.func "42"
```

これは `define_typed_method` に渡したブロック内の `self` が X のインスタンスではなくて『`define_typed_method` を呼び出したコンテキスト（上記の場合では `X`）』になってしまうからです。

```ruby
class X
	define_typed_method(:func, String){ |x|
		self
	}
end

x = X.new
p x.func "42"
# => X
```

これを回避するために [`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) を経由してブロックの呼び出しを行います。

```ruby
class Module
	def define_typed_method name, *sig, &defmethod
		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args|
				# instance_exec 経由で呼び出すことでコンテキストをインスタンスオブジェクトにする
				valid === args ? instance_exec(*args, &defmethod) : super(*args)
			}
		}
	end
end


class X
	def twice x
		x + x
	end

	define_typed_method(:func, String){ |x|
		# ここのコンテキストは X のインスタンスオブジェクトになる
		twice x.to_i
	}
end

x = X.new
p x.func "42"
# => 84
```

これにより `define_typed_method` のブロック内でも他のインスタンスメソッドを呼び出すことができます。


#### 問題点：[`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) にはブロック引数を渡すことができない（未解決）

[`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) で先ほどの問題は回避することができましたが、[`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) を使用した場合では別の問題が発生します。
[`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) では以下のようにブロック引数に対して任意の引数を渡す事ができます。

```ruby
show = proc { |fmt| printf(fmt, self) }

42.instance_exec "%04d\n", &show
# => 0042
```

上記の場合は `show` に渡す引数を [`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) 経由で渡しています。

では、`show` に対してブロック引数を渡したい場合はどうでしょう。

```ruby
# ブロックを受け取って処理を行う
show = proc { |fmt, &block| ... }

# #instance_exec に対してブロック引数も渡したいがすでに &show をブロック引数として渡してる
42.instance_exec("%04d\n", ???, &show)
```

[`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) にはすでに `&show` をブロック引数として渡しているので『`show` で受け取るためのブロック引数を [`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) で渡すこと』ができません。
うーん、ややこしいですね。
これを回避するには [`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) に対して『複数のブロック引数を渡す必要』がありますが、残念ながら Ruby ではそれを行うことができません。

さて、`define_typed_method` の話に戻ります。
`define_typed_method` でも同様の問題が発生し、

```ruby
class Module
	def define_typed_method name, *sig, &defmethod
		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args, &block|
				valid === args ?
					# 呼び出すメソッドに対してブロック引数も渡したいができない
# 					instance_exec(*args, &block, &defmethod)
					instance_exec(*args, &defmethod) :
					# super() は普通に渡せるが…
					super(*args, &block)
			}
		}
	end
end


class X
	define_typed_method(:func, Integer){ |x, &block|
		p x
		# => 42
		p block
		# => nil
	}
end

x = X.new
# ブロック引数を渡したいが…
x.func(42){}
```

これに関しては Ruby の標準機能だけで解決するのはちょっと難しいので、後ほど gem を使った回避方法を記述します。


## 型と一緒に変数名も定義したい

ここからちょっと複雑になってきます。
現状の仕様では、

* `define_typed_method` にメソッドの引数型を渡す
* `define_typed_method` のブロックでメソッドの引数を受け取る

という風になっています。
しかし、以下のように『型と変数名』を一緒に定義したほうがすっきりしますよね。

```ruby
class X
	attr_accessor :name, :age

	# こんな感じで Hash 引数を使用して変数名と型を一緒に渡したい
	define_typed_method(:set, name: String, age: Integer){
		# 引数を自身に代入
		self.name = name
		self.age  = age
	}
end
```

これを実装していきたいと思います。
とりあえず、`Hash` 版の実装を `define_typed_method_with_hash` として定義します。

```ruby
class Module
	def define_typed_method name, *sig, &defmethod
		# sig が Hash なら Hash 版を呼び出す
		return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args|
				valid === args ? instance_exec(*args, &defmethod) : super(*args)
			}
		}
	end

	# とりあえず、別メソッドとして定義
	def define_typed_method_with_hash name, sig, &defmethod
		# この内部で実装していく
	end
end
```

この `define_typed_method_with_hash` 対して実装を記述していきます。


#### 変数をメソッドとして定義する

まずは、`name` や `age` のような変数名で参照する為に `name` や `age` をメソッドとして定義します。

```ruby
class Module
	def define_typed_method name, *sig, &defmethod
		return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args|
				valid === args ? instance_exec(*args, &defmethod) : super(*args)
			}
		}
	end

	def define_typed_method_with_hash name, sig, &defmethod
		# Hash の値が型なのでそれを、define_typed_method に渡して型チェックを行うメソッドを定義する
		define_typed_method(name, *sig.values){ |*args|
			# メソッド内部の実装

			# 変数名で参照する為に Hash のキーを名前としたメソッドを定義する
			sig.keys.each_with_index { |name, i|
				# 引数の値を返す(特異)メソッドを定義
				define_singleton_method name, &args[i].method(:itself)
			}

			# 引数を渡さないでメソッドの実装を呼び出す
			instance_exec &defmethod
		}
	end
end
```

こんな感じで『`Hash` のキーを名前として引数を返すメソッド』を定義することで `name` や `age` などを変数のように参照することができます。

```ruby
class X
	attr_accessor :name, :age
	define_typed_method(:set, name: String, age: Integer){
		p "#{name} : #{age}"
		# => "homu : 14"

		# name や age などがメソッドとして定義されているので擬似的に変数を参照できる
		self.name = name
		self.age  = age
	}
end

x = X.new
x.set("homu", 14)
```


#### インスタンスオブジェクトに直接メソッドを定義しない

これで変数名で参照することはできますが、インスタンスオブジェクトに直接(特異)メソッドを定義するのは大変危険です。

```ruby
class X
	attr_accessor :name, :age
	define_typed_method(:set, name: String, age: Integer){
		# ...
	}
end

x = X.new

# これの内部で #name メソッド等が書き換えられてしまう
x.set("homu", 14)

# 内部の値を書き換えても
x.name = "mami"

# 先ほど #set で上書きされたメソッドが呼び出される
p x.name
# => "homu"
```

この問題を解決するために『変数を参照するメソッドを別のオブジェクトで定義』して、それをコンテキストとしてメソッドを呼び出してみたいと思います。

```ruby
class Module
	def define_typed_method name, *sig, &defmethod
		return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args|
				valid === args ? instance_exec(*args, &defmethod) : super(*args)
			}
		}
	end

	def define_typed_method_with_hash name, sig, &defmethod
		define_typed_method(name, *sig.values){ |*args|
			# 変数を参照するメソッドを定義するオブジェクトを生成
			Object.new.instance_exec {
				# 生成したオブジェクトの内部で変数を参照するメソッドを定義する
				sig.keys.each_with_index { |name, i|
					define_singleton_method name, &args[i].method(:itself)
				}

				# 変数を参照するメソッドが呼び出せるように
				# #instance_exec で set メソッドのコンテキストをこのオブジェクトにする
				instance_exec &defmethod
			}
		}
	end
end


class X
	attr_accessor :name, :age
	define_typed_method(:set, name: String, age: Integer){
		# ...
	}
end

x = X.new

# 書き換えるのは別のオブジェクトのメソッドなので
x.set("homu", 14)

# 内部の値を書き換えても
x.name = "mami"

# x のメソッドは上書きされない
p x.name
# => "mami"
```

このように `Object.new` を利用して動的にオブジェクトを定義して、そのオブジェクトに対して変数を参照するメソッドを定義します。


#### X のメソッドを参照出来るようにする

『別のオブジェクトを生成すること』で副作用なく『変数を参照するメソッド』を定義する事ができました。
しかし、この『別のオブジェクトををコンテキストにする』ことによりメソッド内のコンテキストが変わってしまい、`X` のメソッドにアクセスすることができなくなってしまいました。

```ruby
class X
	attr_accessor :name, :age
	define_typed_method(:set, name: String, age: Integer){
		# self が Object.new になる
		p self
		# => #<Object:0x000000000175dba8>
		# 当然 X のインスタンスオブジェクトではないので X#name を呼ぼうとするとエラーになる
# 		self.name = name
	}
end
```

そこで、[`method_missing`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html) を利用して『参照したメソッドが存在しなければ元のコンテキストのメソッドを参照する』というような処理を追加します。


```ruby
class Module
	def define_typed_method name, *sig, &defmethod
		return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args|
				valid === args ? instance_exec(*args, &defmethod) : super(*args)
			}
		}
	end

	def define_typed_method_with_hash name, sig, &defmethod
		define_typed_method(name, *sig.values){ |*args|
			# 変数を参照するメソッドを定義するオブジェクトを生成
			receiver = self
			Object.new.instance_exec {
				sig.keys.each_with_index { |name, i|
					define_singleton_method name, &args[i].method(:itself)
				}

				# method_missing を利用して参照するメソッドが存在しない場合は
				define_singleton_method(:method_missing){ |name, *args, &block|
					# 元々のレシーバのメソッドを参照するようにする
					receiver.__send__ name, *args, &block
				}

				instance_exec &defmethod
			}
		}
	end
end


class X
	attr_accessor :name, :age
	define_typed_method(:set, name: String, age: Integer){
		# self は Object.new のままだが
		p self
		# => #<Object:0x000000000175dba8>

		# name= や age= メソッドは定義されていないが
		# method_missing 経由で X のメソッドが呼び出される
		self.name = name
		self.age  = age
	}
end

x = X.new

x.set("homu", 14)
p x.name
# => "homu"
p x.age
# => 14
```

このように [`method_missing`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html) を利用することで擬似的に X のメソッドを参照する事ができます。


#### 問題点：インスタンス変数を参照できない（未解決）

[`method_missing`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html) を介すことで X のメソッドを参照する事ができました。
しかし、 [`method_missing`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html) でメソッドを参照することはできても『インスタンス変数』を参照することはできません。

```ruby
class X
	attr_accessor :name, :age
	define_typed_method(:set, name: String, age: Integer){
		# メソッド経由でインスタンス変数を参照できても
		self.name = name

		# 直接インスタンス変数を参照することはできない
		@age = age
	}
end

x = X.new

x.set("homu", 14)
p x.name
# => "homu"
p x.age
# => nil
```

この問題を解決するためには『存在しない変数を参照した時』にフックできる `variable_missing` のようなメソッドが必要なのですが、残念ながらこのようなメソッドは存在しません。
この問題を解決するためには別のアプローチが必要になってきますが、これも標準の機能では解決できないので後で解決方法を記述します。


## まとめ

と、言うことで当初やりたかった

* メソッドの引数に対して型チェック(精査)を行う
* 型によってメソッドの多重定義を行う

という実装できたので一旦まとめます。

#### 実装コード

```ruby
class Module
	def define_typed_method name, *sig, &defmethod
		return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args|
				valid === args ? instance_exec(*args, &defmethod) : super(*args)
			}
		}
	end

	def define_typed_method_with_hash name, sig, &defmethod
		define_typed_method(name, *sig.values){ |*args|
			receiver = self
			Object.new.instance_exec {
				sig.keys.each_with_index { |name, i|
					define_singleton_method name, &args[i].method(:itself)
				}

				define_singleton_method(:method_missing){ |name, *args, &block|
					receiver.__send__ name, *args, &block
				}

				instance_exec &defmethod
			}
		}
	end
end
```

#### 使用コード

```ruby
class Person
	attr_accessor :name, :age

	def initialize
	end

	# name は String
	# age  は Integer
	# で受け取る initialize メソッドを定義
	define_typed_method(:initialize, name: String, age: Integer){
		# Hash のキーで変数を参照する
		self.name = name
		self.age  = age
	}

	define_typed_method(:set, name: String, age: Integer){
		self.name = name
		self.age  = age
	}

	def print
		puts "name:#{name} age:#{age}"
	end

	# フォーマットを渡して出力したり
	define_typed_method(:print, fmt: String){
		printf(fmt, name, age)
	}
end

homu = Person.new("homu", 14)

homu.print
# => name:homu age:14

homu.print("%s : %d\n")
# => homu : 14


mami = Person.new
mami.set("mami", 15)
mami.print
# => name:mami age:15
```

いい感じですね。


#### 問題点

* メソッドにブロック引数を渡すことができない
* メソッド内でインスタンス変数を参照する事ができない


#### 欠点

* 1つメソッドを定義するごとにモジュールを mixin しているので継承リストがどんどん増えていく
* Ruby の言語機能だと『mixin したモジュールを削除する事が出来ない』ので定義したメソッドを削除するのが難しい
* mixin しまくってるので明示的にスーパークラスのメソッドを呼ぶことが出来ない
* Refinements で `#===` を定義しても内部で `using` することができない（影響を受けない


#### 所感

と、いうことで簡単に『型チェックを行う処理』を実装してみました。
ポイントとしては

* 型チェックとは何かを考える
  * 今回は型チェックというものを抽象化して `#===` にフォーカスを当てた
  * あまり Ruby で型というものを意識しないほうがいい気はする
* mixin して型チェックを行うメソッドを追加していく
  * 個人的に Ruby らしくて好き
* メソッド内のコンテキストを意識する
  * どうやってブロックを呼び出すのか
  * ブロック内のコンテキストを何にするのか

という感じでしょうか。
単にメソッド定義をラップするだけならそこまで難しくはありませんが、コンテキストを意識し始めるとちょっと複雑になってきますね。
とはいえ、その割にはコード量自体は思ったよりも少なくなったので個人的には満足。
今回の実装では mixin を利用したり、動的にオブジェクトを生成したり、`method_missing` したりと結構 Ruby らしいコードにはなったような気はします。

あと型チェックを『`#===` でチェックする』と抽象化したので思ったよりも拡張性は高いです（理由は後述。
ちなみに似たようなライブラリをつくっているので気になる方はこちらも参照してみください（リファクタリングしたまま放置中なので近々破壊的変更する予定ですが…。

* [Ruby でメソッドを多重定義するためのライブラリをつくった - Secret Garden(Instrumental)](http://secret-garden.hatenablog.com/entry/2016/06/07/001100)


と、言うことで簡単ですが『Ruby で型チェック』を行ってみました。
Ruby のメタプログラミングたのしいのでみんなもやってみましょう。
そしてここから<del>が本番</del>は番外編です。


## クラスオブジェクト以外で型チェックする

さて、今まで散々説明してきましたが型チェックは `#===` で行っています。
つまり『`#===` が定義されているオブジェクト』であれば別にクラスオブジェクトである必要はありません。
Ruby では `#===` が `when` で使用されることを想定しているのでクラスオブジェクト以外にも様々なオブジェクトで `#===` が定義さいれています。

と、いうことでクラスオブジェクト以外でもいろいろと試してみましょう。

```ruby
class X
	# Regexp で受け取る文字列を制限する
	# 文字列が http の URL のみ受け付ける
	define_typed_method(:get, url: /http:.*/){
		url
	}

	# Range で受け取る範囲を制限
	# 1〜12のみ受け付ける
	define_typed_method(:month, month: (1..12)){
		"#{month}月"
	}


	# Proc で任意の条件を設定
	# サイズが 5 以下のオブジェクトのみ受け付ける
	define_typed_method(:func, x: -> x { x.size <= 5 }){
		x.size
	}
end


x = X.new

p x.get "http://example.com"
# => "http://example.com"

# Error
# x.get "ftp://example.com"


p x.month 2
# => "2月"

# Error
# p x.month 0

p x.func [1, 2, 3, 4, 5]
# => 5
p x.func "homu"
# => 4

# Error
# p x.func (1..10)
# p x.func "homuhomu"
```

上記のコードのように `Regexp`、`Range`、`Proc` ではそれそれ独自に `#===` を定義しているので型チェックのような形で引数を精査する事ができます。
このように『`#===` で型チェックすること』でクラスオブジェクト以外にも様々なオブジェクトに対して使用することができるので割と柔軟性は高いです。


## `Array#===` を定義する

さてさて、`Array#===` を定義することで次のように利用することもできます。


```ruby
class Array
	def === other, &block
		size == other.size && zip(other).all? { |a, b| a.=== b, &block }
	end
end


class Person
	attr_accessor :name, :age

	# set [name, age]
	# みたいな配列で渡す
	define_typed_method(:set, data: [String, Integer]){
		self.name = data[0]
		self.age  = data[1]
	}
end


mado = Person.new
mado.set(["mado", 14])

p mado.name
# => "mado"
p mado.age
# => 14
```

こんな感じで簡単に型チェックするオブジェクトを拡張する事ができます。
今回は実装しませんでしたが、`Hash#===` なんかも定義してみると面白そうですね。


## `#instance_exec` にブロック引数を渡す

さて、ここからは先程解決できなかった問題を解決していきます。
まずは、『[`#instance_exec`](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_exec.html) にはブロック引数を渡すことができない』問題です。

これは [`proc-unbind`](https://github.com/osyo-manga/gem-proc-unbind) を利用して解決します。
[`proc-unbind`](https://github.com/osyo-manga/gem-proc-unbind) を使用することで『`Proc` オブジェクトを任意のコンテキスト』で呼び出す事ができます。

#### インストール

```shell
$ gem install proc-unbind
```

#### 使い方

```ruby
require "proc/unbind"

using Proc::Unbind

expr = proc { |*args, &block|
	p self
	p args
	p block
}

# unbind することで UnboundMethod を返す
expr_unbind = expr.unbind

p expr_unbind.class
# => UnboundMethod


# 任意のオブジェクトで bind する
# bind 後に call を呼び出すことで
# bind したオブジェクトのコンテキストで expr を評価する
expr_unbind.bind(42).call(1, 2, 3, &:to_s)
# => 42
# => [1, 2, 3]
# => #<Proc:0x00000000025a6b10>


expr_unbind.bind("homu").call {}
# => "homu"
# => []
# => #<Proc:0x0000000001012088@/tmp/vnzeaBx/11738:27>
```

こんな感じで `Proc#unbind` から `UnboundMethod` を生成します。
`UnboundMethod` は任意のオブジェクトをバインドすることでそのオブジェクトをレシーバとして処理を呼び出す事ができます。

さて、これを利用して `define_typed_method` でブロック引数を渡すことが出来るようにしてみましょう。

```ruby
require "proc/unbind"


class Module
	using Proc::Unbind

	def define_typed_method name, *sig, &defmethod
		return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args, &block|
				# proc/unbind を利用してレシーバを self にしながらブロックを渡す
				# rebind(self) は unbind.bind(self) と同等
				valid === args ? defmethod.rebind(self).call(*args, &block) : super(*args, &block)
			}
		}
	end

	def define_typed_method_with_hash name, sig, &defmethod
		define_typed_method(name, *sig.values){ |*args, &block|
			receiver = self
			Object.new.instance_exec {
				sig.keys.each_with_index { |name, i|
					define_singleton_method name, &args[i].method(:itself)
				}

				define_singleton_method(:method_missing){ |name, *args, &block|
					receiver.__send__ name, *args, &block
				}

				defmethod.rebind(self).call &block
			}
		}
	end
end


class X
	# ブロック引数はブロックの引数として受け取る
	define_typed_method(:func){ |&block|
		block.call 42
	}
end

p X.new.func { |x| x + x }
# => 84
```

`|&block|` のようにブロック引数を受け取る必要はありますが、これでブロック引数を渡して受け取る事ができました。
これで問題の1つであった『メソッドにブロック引数を渡すことができない』ということが解決できましたね！！


## メソッド内でインスタンス変数を参照出来るようにしてみる(仮)

先に結論から書いておくと一応対応してみましたが、これでも完全な解決にはなりません。
なのでまあこういうアプローチもあるよーぐらいな感じで書いていきます。

さて、そもそもの問題点として『メソッド内から引数を変数名で参照したい』を解決するための手段として

* 変数を参照するメソッドを定義したオブジェクトを新しく生成する
* そのオブジェクトを経由して変数を参照する
* 元のレシーバには `method_missing` 経由で参照する

という手段を用いました。
しかし、この結果、レシーバのコンテキストが異なってしまい新たな問題が発生してしまいました。

そこでアプローチを変えて以下のような感じにしてみたいと思います。

* 変数を参照するメソッドを定義した『モジュール』を新しく生成する
* そのモジュールをレシーバに mixin する
* ブロックを呼び出すを呼び出す
* mixin したモジュールを削除する

まあつまり『一時的にモジュールを mixin する』ということで副作用を最小限に抑えようと言うようなアプローチになります。

じゃあ、どうやって『一時的にモジュールを mixin する』のかというと Ruby の標準の機能ではできないので [`gem-unmixer`](https://github.com/osyo-manga/gem-unmixer) を使います。
[`gem-unmixer`](https://github.com/osyo-manga/gem-unmixer) に関しては以下の記事を参照してください。

* [Ruby で mixin したモジュールを削除する gem をつくった - Secret Garden(Instrumental)](http://secret-garden.hatenablog.com/entry/2017/02/27/000027)

今回はこれを利用して先ほどのような機能を実装します。

```ruby
require "proc/unbind"
require "unmixer"


class Module
	using Proc::Unbind
	using Unmixer

	def define_typed_method name, *sig, &defmethod
		return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

		valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
		prepend Module.new {
			define_method(name){ |*args, &block|
				valid === args ? defmethod.rebind(self).call(*args, &block) : super(*args, &block)
			}
		}
	end

	def define_typed_method_with_hash name, sig, &defmethod
		define_typed_method(name, *sig.values){ |*args, &block|
			# キーを変数名として参照するためのモジュールを定義する
			accessor = Module.new {
				sig.keys.each_with_index { |name, i|
					define_method name, &args[i].method(:itself)
				}
			}

			# defmethod を呼び出す時のみ一時的に mixin する
			extend(accessor){
				# このブロック内では accessor モジュールが有効になっている
				return defmethod.rebind(self).call &block
			}
		}
	end
end


class X
	attr_accessor :name, :age

	define_typed_method(:set, name: String, age: Integer){
		p self.name
		# self は X のインスタンスオブジェクト
		p self
		# => #<X:0x0000000001e98508>

		# なので変数名などを直接参照できる
		@name = name
		@age  = age
# String か Symbol のみを受け付ける
define_typed_method(:func, str: (String | Symbol)){
	str.capitalize
}
	}
end

x = X.new
x.set("homu", 14)
p x.name
# => "homu"
p x.age
# => 14
```

これで、コンテキストを X のインスタンスオブジェクトにしたままブロック内で変数が参照できるようになりました。
やったね！！

ただし、この実装でも『ブロック内以外（例えば、ブロック内から呼び出した他のメソッド内とか）』でも mixin したモジュールの影響を受けてしまうので解決したとはいえません。
うーん、アプローチとしてはいいと思ったんですがむずかしいですね…。


## 複数の型でチェックする

最後の最後に本当のおまけです。
例えば以下のように複数の型を許容したい場合があると思います。

```ruby
# String か Symbol のみを受け付ける
define_typed_method(:func, str: (String | Symbol)){
	str.capitalize
}
```

これを解決する場合 [`gem-laurel`](https://github.com/osyo-manga/gem-laurel) が利用できます（と、いうかこういう目的のために作った gem になります。


#### インストール

```shell
$ gem install laurel
```


#### 使い方

```ruby
require "laurel"

using Laurel::Refine

(x & y).any_method # => x.any_method && y.any_method
```

こんな感じで `(a & b).hoge` と呼び出した場合、`a.hoge && b.hoge` と評価するようなライブラリになります。
つまり `(String | Symbol) === a` と呼びだされた場合は `String === a || Symbol === a` となるような感じですね。


```ruby
require "laurel"

using Laurel::Refine

class X
	# String か Symbol のみを受け付ける
	define_typed_method(:func, str: (String | Symbol)){
		str.capitalize
	}

	# こんな感じで Regexp を組み合わせたり
	# 文字列かつ、数値なら呼ばれる
	define_typed_method(:twice, n: (String & /^-?\d+$/)){
		n.to_i + n.to_i
	}

	# 数値の場合は普通に計算
	define_typed_method(:twice, n: Integer){
		n + n
	}
end

x = X.new
p x.func "homu"
p x.func :mado

# Error
# p x.func 42

p x.twice "-6"
# => 12
p x.twice "5"
# => 10
p x.twice 42
# => 84

# Error
# p x.twice ""
# p x.twice "-"
# p x.twice "-42homu"
```

これでかなり引数に対する制約が柔軟に定義しやすくなったと思います。


## 所感

と、調子にのって書きまくったらめっちゃ長くなってしまいました…。
まあこんな感じでお手軽に Ruby でも型チェックできるよーという感じの内容でしたね！！

Ruby で型チェックを行う機構自体は前からいろいろと考えているんですが、型チェック自体は今回のように `#===` みたいなメソッドでダックタイピングするのがいいような気はします。
拡張性や柔軟性も高いですし。
最初は `Type` クラスみたいなのも考えてみたんですが、こっちは『Ruby に置いて型とはなんなのか』みたいなのが定義できなかったので考えるのをやめました…。

ただ、型チェックを考えるにあたって型チェック自体よりも

* 定義するメソッド（ブロック）の呼び出し方
* て型チェックを行うメソッドの定義方法
  * `define_typed_method` みたいなのでラップするのがよいのか？

みたいな『どうやって型チェックを行うメソッドを定義するべきか』みたいなところで悩んだりすることが多かったです。
今回は `define_typed_method(:func, name: String, age: Integer)` みたいに `define_methodo` をラップするような形にしてみましたが。

```ruby
requires [String, Integer]
def func name, age
end
```

みたいな感じで定義したほうがすっきりしそうな気もしますし、もしくは

```ruby
def_.func(name: String, age: Integer){

}
```

みたいな定義方法も考えられますしね。
この辺りは好みもあるし、1人で考えていても答えが見つからないと思うんですが、いかんせん相談できる Rubyist がまわりに少ないので厳しい…。
<del>slack とかに ruby-jp みたいなコミュニティができないかなー</del>

あとは今のところ動的に型チェックを行うことを想定していますが、じゃあ静的に型チェックするには…みたいなのも考える必要が出てきますね。
今のところパフォーマンスに関しては完全に考えていないですし、ここら辺を考えると更にむずかしくなってきますね…。

まあそんな感じで『Ruby で型チェックを行う』というのを考えてみました。
これがベストだとは思いませんが、他の方も型に関して興味を持ってもらえると嬉しいです。
なにか質問や気になる点があればコメントや Twitter なんかで聞いてください。