wat-aro
Ruby
の型のおさらいRuby
の型シグナチャruby-signature
の記法untyped
Record type
{ id: Integer, name: String }
Interface type
例: each メソッドを持っている型
interface _Each[A, B]
def each: { (A) -> void } -> B
end
Literal type
1
という型は Integer かつ 値を 1
に限定:to_s
という型を定義可能Union type
Integer | String
は Integer or StringIntersection type
Integer & String
は Integer and String{ id: Integer } & { name: String }
は { id: Integer, name: String }
Optional type
Integer?
で Integer | nil
@name: String
で ivardef to_s: () -> String
でメソッドを定義ruby-signature はまだ StandardLibrary の片付けが終わっていないので
適当なものに型をつけてみる
abbrev(words, pattern = nil)
Abbrev.abbrev(['ruby'])
#=> {"ruby"=>"ruby", "rub"=>"ruby", "ru"=>"ruby", "r"=>"ruby"}
Abbrev.abbrev(%w{ car cone })
#=> {"ca"=>"car", "con"=>"cone", "co"=>"cone", "car"=>"car", "cone"=>"cone"}
Abbrev.abbrev(%w{car box cone crab}, /b/)
#=> {"box"=>"box", "bo"=>"box", "b"=>"box", "crab" => "crab"}
Abbrev.abbrev(%w{car box cone}, 'ca')
#=> {"car"=>"car", "ca"=>"car"}
String の配列をうけとり、String を分解して key とし、元の String を value とするHashを返す
Abbrev.abbrev(['ruby'])
#=> {"ruby"=>"ruby", "rub"=>"ruby", "ru"=>"ruby", "r"=>"ruby"}
これに型を付けると
module Abbrev
def self?.abbrev: (Array[String]) -> Hash[String, String]
end
def self?
は module function 用の書き方
空配列を受け取っても空ハッシュを返すだけなので問題なし。
example を見ると String と Regexp を受け取ることを想定しているよう
Abbrev.abbrev(%w{car box cone crab}, /b/)
#=> {"box"=>"box", "bo"=>"box", "b"=>"box", "crab" => "crab"}
Abbrev.abbrev(%w{car box cone}, 'ca')
#=> {"car"=>"car", "ca"=>"car"}
じゃあ Integer とか渡したらエラーになるのかな?
Abbrev.abbrev(%w{12345}, 1)
#=> {}
エラーにならないだと…
この場合って型はどうつければいいのか。
実装を abbrev の実装を見てみると
!~
を直接使っているInteger#!~
は常に true を返すdef abbrev(words, pattern = nil)
...
if pattern.is_a?(String)
pattern = /\A#{Regexp.quote(pattern)}/ # regard as a prefix
end
words.each do |word|
next if word.empty?
word.size.downto(1) { |len|
abbrev = word[0...len]
next if pattern && pattern !~ abbrev
...
}
end
end
words.each do |word|
next if pattern && pattern !~ word
table[word] = word
end
table
end
{}
を返すようになっているだけに見えるThe optional pattern parameter is a pattern or a string.
Only input strings that match the pattern or start with the string are included in the output hash.
pattern とは言っているけれど、それがRegexpとは限定していない。
何かのメソッドの返り値がStringやRegexpだと思っていたのにIntegerだった場合
def some_method: () -> (String | Integer)
pattern = some_method()
Abbrev.abbrev(["12345"], pattern)
#=> {}
Integerが来てここでエラーにならなくてもその後で不整合がおこる場合に困る。
ここを通るテストがない場合になぜ駄目なのかの調査が必要になる。
それよりも型検査で弾いてくれたほうが嬉しい。
module Abbrev
def self?.abbrev: (Array[String], ?(String | Regexp | nil)) -> Hash[String, String]
end
第2引数は String, Regexp, nil としました。
この定義でruby-signatureにPRを送ってマージされた。