Julia: 値と変数に対する Type Annotation の違い

はじめに

Julia Advent Calendar 2015 8日目の記事です。

この記事では、値 (value) と変数 (variable) に対する type annotation の違いを、問題とそれに対する解答を用意する形式で説明しようと思います。そんなの知ってるぜ!という方は、問題だけ解いてみて自分の理解度を試してもらえればと思います。

記事に出てくるJuliaコードは、Julia 0.5-dev, 0.4.0 で動作確認しました。

問題

新規REPLセッションを開いて、A、B それぞれを実行したときの挙動はどうなるでしょうか?エラーの発生の有無と、エラーが発生しない場合は返り値の値、型を答えてください。

A

function f()
    x = (1.0 + 2.0)::Int
    return x
end

f()

B

function g()
    x::Int = (1.0 + 2.0)
    return x
end

g()

なお、一方ではエラーが起き、もう一方はエラー無く実行されます。一見似たような書き方ですが、二つは異なる意味を持ちます。この記事ではそれぞれを解説しようと思います。

この問題の答えがわからなかった方は、この記事を読むと正解がわかるはずなので、続きをご覧ください。下の方に、簡潔な問題の解答とおまけ問題を書いておきました。

A: 値に対する type annotation

Aの2行目では、値に対して type annotation をしています。これは typeassert とも呼びます。Aで使った type annotation を日本語で説明してみると、「(1.0 + 2.0) という式を評価した値は、Int 型であることを保証する」となります。

(1.0 + 2.0) を評価した値は 3.0 であり、 Float64の型を持ちます。したがって Float64 != Int であるため、

ERROR: TypeError: typeassert: expected Int64, got Float64

のような typeassert のエラーが吐かれます。

(1.0 + 2.0)を評価した値の型は一見して明らかため、実用的な例ではありませんが、例えば関数の返り値の型は一見してわからないことがあるので、例えば以下のような書き方は有用な場合もあると思います。

x = f(y)::Int

B: 変数に対する type annotation

Bの2行目では、変数に対して type annotation をしています。同じく日本語で説明すると、「xという変数に入る値は、Int 型であることを保証する」となります。また、値に対する annotation とは異なりスコープを持ちます。

前述したとおり、(1.0 + 2.0) を評価した値は 3.0 であり、Float64の型を持ちます。一方で、x は Int型の値を持つ変数として宣言されているため、この場合、Float64型である (1.0 + 2.0) を、Int 型に変換するような処理が暗黙的に行われます。したがって、変換可能な場合には(B の例がそうです)、エラーは起きません。暗黙的に処理が行われるというのは、知らないと予期せぬバグに遭遇することになるため、気をつける必要があります。

では、変数に対する type annotation はどのような場合に使うかというと、あるスコープの範囲で、代入によって変数の型が変わってしまうのを防ぐために使います。ある変数の型がスコープの範囲で不変というのはコンパイラにとっては嬉しい事で、パフォーマンスの向上に繋がります。Performance tips にもありますね(参考: Performance tips / Avoid changing the type of a variable

違いまとめ

ここまでの話から、違いをまとめると、以下のようになります。

Type annotation の種類 typeassert error  暗黙的な型変換  スコープ 
値に対する type annotation あり なし なし
変数に対する type annotation   なし あり あり


最後に

type annotation を使うときは、値と変数に対する annotation の違いを意識して、使い分けましょう

問題の解答

  • A: typeassert に引っかかり、TypeError が吐かれる
  • B: Int 型の 3 が返り値として得られる

おまけ問題

1

function h()
    x::UInt8 = UInt8(0)
    x = Float64(0.0)
    x
end
# なんと表示されるでしょうか?
println(typeof(h()))

2

function s()
    x::Int = Float64(0)
    x = UInt8(0)
    x = Float32(0.5)
    x
end
# なんと表示されるでしょうか?
s()

解答は、各自REPLで実行して確認してみてください。長々と読んでくださりありがとうございました。

参考