Rubyに型を付けるRubypeというgemを作ったヨ！

# 2020/12/27追記

https://www.ruby-lang.org/ja/news/2020/12/25/ruby-3-0-0-released/
=> Ruby3.0.0にRBSとTypeProfと用いた静的解析に対するソリューションが提供されました。

今後はこちらのエコシステムをガンガン盛り上げていきましょう💪


#あいさつ

## P.S. 12/11 HaskellからRubypeにgemを変更しました

こんにちは. 

寒さが一段と厳しくなってきた事もありまして心を温めるGemを作りました.

#### Rubyの振る舞いを汚染する事無く型保証の恩恵をゆるふわ受けられるgemです

（型をつけるとか型保証という言葉をこの文脈ではメソッドの引数返り値のクラスを実行時にチェックするという意味で使っている.）

得体の知れないGemで抵抗感があるかもしれませんが、コード自体は50行以下の薄いGemなので気軽にさくっと副作用を避け型保証を導入したい方に強くオススメします.

#### [Giuhub](https://github.com/gogotanaka/Rubype)
#### [Rubygems](https://rubygems.org/gems/rubype)

いつも通りのメソッドにプラガブルな型情報を付け加えるだけで引数と返り値の型を保証する事が出来ます、型情報の付加を除くメソッドの記法やふるまいはあなたの知っているRubyのままです.

（型なしメソッドと型ありメソッドの共存も可能）

MatzことRubyの作者の松本さんが3.0（次世代Ruby）での型導入を示唆されていましたが、イメージがあまりつかなかったのでどんなもんかgemレベルで試せたらいいなと思いました.

適当なRailsアプリに導入してみましたがイイ感じに動いています（ドッグフード）


# 雰囲気

`gem install rubype` などでgemを持ってきて（Ruby2.0.0+を必要とします）

p.s. 2014/12/07 11:00(JST) シンタックスを大幅に変更しました.
（もはやHaskellの面影なし..）

```rb
require 'rubype'

# 引数の型チェック
class MyClass
  def sum(x, y)
    x + y
  end
  typesig sum: [Numeric, Numeric => Numeric]
end

MyClass.new.sum(1, 2)
#=> 3

MyClass.new.sum(1, 'not numeric')
#=> ArgumentError: Wrong type of argument, type of "not numeric" should be Numeric



# 返り値の型チェック
class MyClass
  def wrong_sum(x, y)
    'string'
  end
  typesig wrong_sum: [Numeric, Numeric => Numeric]
end
MyClass.new.wrong_sum(1, 2)
#=> TypeError: Expected wrong_sum to return Numeric but got "not numeric" instead
```
# 説明

`Module`クラスに定義してある`typesig`メソッド用いて既存のコードにあるメソッド定義に型情報を記述するだけです.

```rb
# rubype gemをrequire
require 'rubype'

# いつも通りのメソッド宣言の後に型情報を加える.
class MyClass
  def sum(x, y)
   # いつも通りのRubyコード
  end
  typesig sum: 型情報
end
```

### 型情報の記述方法

```rb
# 引数が１つのメソッドなら
# (第一引数のクラス) => (返値のクラス)

例. Numeric => Numeric
# Numericクラスのオブジェクトを引数にしてNumericクラスのオブジェクトを返すメソッドである事を保証する


#引数が２つのメソッドなら
#(第一引数のクラス), (第二引数のクラス) => (返値のクラス)

例. Numeric, Numeric => Array
#Numericクラスのオブジェクト2つ引数として取りArrayクラスのオブジェクトを返すメソッドである事を保証する

#引数が３つのメソッドなら
#(第一引数のクラス), (第二引数のクラス), (第三引数のクラス) => (返値のクラス)

例. Numeric, Numeric, Numeric => Array
#Numericクラスのオブジェクト3つ引数として取りArrayクラスのオブジェクトを返すメソッドである事を保証する

...
```

## 例. 人と結婚するのは人だけ
`People`オブジェクトの`marry` メソッドが引数に`People`オブジェクトを取る事を保証する


```rb
class People
  # Anyクラスはどんなオブジェクトでもマッチする.
  def marry(people)
    # 素敵な実装
  end
  typesig marry: [People => Any]
end

People.new.marry(People.new)
#=> 1

People.new.marry('non people')
#=> ArgumentError: Wrong type of argument, type of "non people" should be People
```

# 推したい所

## ありのままのRubyに副作用の少ない形で導入出来る事が特徴です.
振り切ってネタGemにしてもよかったのですが、なくなるべく本番レベルで使える事を念頭におきました.

既存のコードはそのまま動きます.（Module#typesigを定義してなければ）型情報を付けたいメソッドにのみ型情報を付けてください.

## コンパイル時に型チェックとか出来ないけど
コンパイル時に型チェックしてエラー検出という恩恵は現時点では受けられませんが、
メソッド内での引数のクラスチェックなどの記述は省かれ、またエラーが自明になると思います.
ドキュメントとしての役割も果たせます.

## ダックタイピング
返り値の型（クラス）は保証したいけど、引数はちょっと..というあなたのために`Any`クラスを用意してあります.

どんなクラスのオブジェクトとでもマッチするようになっております.

```rb
class MyClass
  def any_meth(any_obj)
    # 素敵な実装
    return 1
  end
  typesig any_meth: [Any => Numeric]
end

MyClass.new.any_meth(1)
#=> no error

MyClass.new.any_meth('string')
#=> no error
```

# これからやりたい事
## 可変長引数, キーワード引数の対応

```rb
def sum(*ary)
  ary.inject(:+)
end
typesig sum: [[Numeric] => Numeric]

```

## エラーハンドリング
型宣言と実際の引数の数が合わないとかはメソッドの宣言の時点で知らせたい.

## 実行前の型チェック
型シグネチャーがついているメソッドを静的解析する
（たとえば#to_iが呼ばれていたらそれは#to_iが定義されているクラスだみたいな）


# 仕組み
各`Module`クラスのオブジェクト（いわゆるモジュール, クラス）に`@__haskell__`という名無しモジュールを持たせてそいつに型チェックを含んだメソッドを付けて`prepend`しています.

イメージ的には`@__haskell__`という型チェックをする薄いモジュールを通して各メソッドを読んでいる感じ.
（`type` で宣言しなかった普通のメソッドはこの限りでなく、普段通りの振る舞いを辿る.）

# 感想
手前味噌ながら、適当に使ってみましたが結構イイです.

メソッドの引数, 返値を実行時に調べるだけのゆるふわ型保証ですが、

それがまたイイです. Rubyの良さを殺してない感じが好きです.

おやすみ.