Crystalの
インスタンス変数の
型推論について
さっき作った (@MakeNowJust)
Crystalの
型推論は
三つある
Crystalの型推論
- 変数に対する型推論
↑説明することが多すぎる - インスタンス変数に対する型推論
↑今回解説するやつ - クラス変数に対する型推論
↑あまり使わないので説明するモチベーションががが
変数に対する型推論
foo = 42
p typeof(foo) # => Int32
foo = "foo"
p typeof(foo) # => String
if rand(0..1) == 0
foo = "foo"
else
foo = 42
end
p typeof(foo) # => Int32 | String
foo = 42
while true
case foo
when Int32 then foo = "foo"
when String then break
end
end
p typeof(foo) # => String
分岐・ループなど
様々なパターンがあって
かなり複雑!!
メソッドの引数の型は
推論されないの?
def foo(x, y)
p x + y
end
foo 1, 2 # => 3
foo "foo", "bar" # => "foobar"
# foo :foo, :bar # raises compilation error!!
メソッドの呼び出しがあったら
実引数の型をとりあえずあてはめてみて
コンパイルができるかを確かめている。
(型制限のない引数の型はC++のテンプレート的な)
インスタンス変数の型推論
- インスタンス変数の型はコンパイル時に
- 決定できなければいけない。
- 型が決定できないとなんか長いエラーが出る。
- でも、必ずしも明示的に型を書く必要はない。
↑なぜ? どういうときに型を書かなくてもいいの?
インスタンス変数の型が決定できなくて
コンパイルできない例
class Foo
def foo
@foo
end
end
p Foo.new.foo
`@foo`に代入せず
いきなり参照している
(Rubyだとこの場合`nil`が返る)
Q.そもそもなんで型を
決定しなきゃいけないの?
A. コンパイルするから。
コンパイルするために型を決定することは必須ではないが
最適化を上手く適用したり、高速なコードを生成するには
型を決定することが必須。
実はエラーに全部書いてある
The type of a instance variable, if not declared explicitly with
`@foo : Type`, is inferred from assignments to it across
the whole program.
The assignments must look like this:
1. `@foo = 1` (or other literals), inferred to the literal's type
2. `@foo = Type.new`, type is inferred to be Type
3. `@foo = Type.method`, where `method` has a return type
annotation, type is inferred from it
4. `@foo = arg`, with 'arg' being a method argument with a
type restriction 'Type', type is inferred to be Type
5. `@foo = arg`, with 'arg' being a method argument with a
default value, type is inferred using rules 1, 2 and 3 from it
6. `@foo = uninitialized Type`, type is inferred to be Type
7. `@foo = LibSome.func`, and `LibSome` is a `lib`, type
is inferred from that fun.
8. `LibSome.func(out @foo)`, and `LibSome` is a `lib`, type
is inferred from that fun argument.
Other assignments have no effect on its type.
雑な訳
- `@foo = 1`みたいにリテラルを代入する場合
→対応する型に推論される - `@foo = Type.new`みたいに`.new`を呼び出してる場合
→`Type`に推論される - `@foo = Type.method`みたいに呼び出したメソッドに
返り値の型制限がある場合
→その型制限の型に推論される - `@foo = arg`みたいに引数を代入していて、
引数に型制限がある場合
→その型制限の型に推論される - `@foo = arg`みたいに引数を代入していて、
引数のデフォルト値が上の1から3のルールで
型推論できる場合
→その型に推論される。
雑な訳(続き)
- `@foo = uninitialize Type`のように
`uninitialize`を使っている場合
→`Type`に推論される - `@foo = LibSome.func`のようにlibの関数を呼び出してる場合
→`LibSome.func`の返り値の型に推論される - `LibSome.func(out @foo)`のように`out`付きで
libの関数を呼び出している場合
→その位置の引数の型に推論される
割と普通な感じ
ちなみに
`src/compiler/crystal/semantic/type_guess_visitor.cr`に
今回説明した辺りの実装がある。
(この`TypeGuessVisitor`はクラス変数の型推論にも使われる)
type guess?
型推論ではなくて、型推測なのでは?
‥‥それな!!
完!
Crystalのインスタンス変数の型推論について
By Kitsune Tsuyusato
Crystalのインスタンス変数の型推論について
- 1,854