Rubyのlambdaを型付き言語としてパースして扱う実行時型検査ライブラリを作った

# 作ったもの

Rubyのなんちゃって実行時型チェッカを作りました。(https://github.com/rike422/moguro)
こういう感じでかけます。

```ruby
class MockClass
  include Moguro::Decorator
  
  # puts_methodの引数をチェック
  pre_c ->(a: Integer, b: String) {
    assert_equal(a, 1)
    assert_equal(b, 'b')
  }
 
  def puts_method(a, b)
    puts b
    a + 1
  end
  
  # post_contract_violation_methodの戻り値をチェック
  post_c -> (first: String) {
    p first
  }
  def post_contract_violation_method
    1
  end
  
end


c = MockClass.new
c.puts_method(1, 'b')
# 1
# => 2

begin 
  c.puts_method('a', 1)
rescue => e
  p e
end
# =>
# Type MissMatch: a is expected (Integer) actual a(String) (Moguro::Errors::ArgumentsTypeMismatchError)
# Expected: [a: (Integer), b: (String)]
# Actual: [a: a(String), b: 1(Integer)]
# Value guarded in: MockClass::puts_method
# At: #{source_location}

begin
  c.post_contract_violation_method
rescue => e
  p e
end

# =>
# Type MissMatch: first is expected (String) actual 1(Integer)
#  Expected: [first: (String)]
#  Actual: [first: 1(Integer)]
#  Value guarded in: MockClass::post_contract_violation_method
#  At: #{source_location}


```

## 上位互換(使うべきもの)

ちょうど概ね完成したあたりで、sorbetが発表されたのでなんとなく悲しい気持ちになりました。

- https://sorbet.run/
- https://github.com/egonSchiele/contracts.ruby
- https://github.com/soutaro/steep
	- https://github.com/south37/yard2steep


## 思いつきのもと

flowtypeをつかってjsを書いていたところあることに気づきました。


```javascript:型付き関数
const lambda = (a: string, b: number) => {
}
```

```ruby:rubyのオプション引数つきlambda
lambda = -> (a: String, b: Integer) {
  p a
}
```
完全に一致してますね。

さらにUnion Type, さらにArray Typeも同じノリでできそうです。

#### Union Type

```javascript:uniontype
const lambda = (a: string | number) => {
}
```

```ruby:エセuniontype
lambda = -> (a: String || Integer) {
  p a
}
```

#### Array Type

```javascript:ArrayType
const lambda = (a: Array<number>) => {
}
```

```ruby:エセArrayType
lambda = -> (a: Array[String,Integer]) {
  p a
}
```

つまりrubyのオプション引数の部分に型を書いたら、ts・flowtypeユーザにとっては割と違和感なく
型がかけるということです。
これに気づいた私は矢も盾もたまらずといった感じで、社内LT用に型チェッカを作ることにしたのでした。

## すごく参考になったライブラリ

- https://github.com/egonSchiele/contracts.ruby

## 実装時に使った・作ったもの

### Decorator

オプション引数で型をつけるといっても、本来の用途で使えないのであれば意味がありません。
そこでDecoratorのスタイルで型を定義できるようにしようと思い立ちました。
こういうときRubyは最高で、動的にModuleクラスのインスタンスを作成し、
受け取ったlambdaを実行したあとsuperを呼ぶ同名のメソッドを定義してあげて、本来のクラスにprependするだけで実装できます。

この辺でやってます
- https://github.com/rike422/moguro/blob/master/lib/moguro/handlers/method_handler.rb#L23

### Lambdaのコードを再解釈

今回はクラスメソッドで受け取ったlambdaのオプション引数部に書いてあるクラスを、
型として扱いチェックを行わなければならないので、lambdaのコードを再解釈する必要があります。
その際に、とても役に立ったライブラリが下の２つです。

- https://github.com/banister/method_source
- https://github.com/whitequark/parser

#### method_source 

rubyのコア機能だけだとsource_locationでの定義場所しか取れませんが、
[method_sourceを使うことによりソースコードをまるっと取れます。](https://github.com/banister/method_source#example-display-method-source)
最高です。

#### パーサ

##### 実装法

ざっくりというとmethod_sourceで受け取ったlambdaのソースコードをパースして、
[キーワード引数のイベントハンドラ(on_kwoptarg)で受け取ったもの](https://github.com/rike422/moguro/blob/master/lib/moguro/processors/contract_processor.rb#L32)を、
[更に引数解析用のパーサに突っ込む](https://github.com/rike422/moguro/blob/master/lib/moguro/processors/arguments_processor.rb#L14)ことで、定数評価などのイベントが発生し型定義を取得することができます。

受け取った型に応じて、[型チェック用のクラス](https://github.com/rike422/moguro/tree/master/lib/moguro/types)のインスタンスを保存しておき、`method.parameters`で受け取った引数名をキーにして、値を型チェッカに渡しています。


### Assert

開発開始あたりで、[t-wadaさんのスライド](https://speakerdeck.com/twada/php-conference-2016)
を見直していて、このGemを使って事前条件と事後条件のチェックを行えば良いんじゃないかと思い立ちました。
現在は直接includeされたクラスにTest::Unit::Assertionsを突っ込んじゃっていますが、
理想としては対象のオブジェクトの状態をコピーしたサンドボックス用のクラスを作ってあげて、
そのクラスに`Test::Unit::Assertions`をincludeしておいてというのが理想です。

### パフォーマンス

測ってないですが相当落ちると思います。

```ruby
Moguro.enabled = false
```

対象クラスの読み込み前にを実行してあげれば、上記の解釈などの処理は行われません。

## まとめ

みなさんもrubyのシンタックスを再定義していきましょう！！！

### 名前の元ネタ

ドーン