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?

型推論ではなくて、型推測なのでは?

 

‥‥それな!!

完!

Made with Slides.com