75
74

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2014-12-06

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

Rubygems

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

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

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

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

雰囲気

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

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

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メソッド用いて既存のコードにあるメソッド定義に型情報を記述するだけです.

# rubype gemをrequire
require 'rubype'

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

型情報の記述方法

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

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


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

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

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

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

...

例. 人と結婚するのは人だけ

Peopleオブジェクトのmarry メソッドが引数にPeopleオブジェクトを取る事を保証する

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クラスを用意してあります.

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

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

これからやりたい事

可変長引数, キーワード引数の対応

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

エラーハンドリング

型宣言と実際の引数の数が合わないとかはメソッドの宣言の時点で知らせたい.

実行前の型チェック

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

仕組み

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

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

感想

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

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

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

おやすみ.

75
74
14

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
75
74

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?