ユニ

ユニオン型 の Crystal

1個の演習

ユニオン型について

Crystalでは、変数が複数の型から成ることができます。 これはユニオン型と呼ばれます。 Crystalでは、コンパイラーがユニオン型を推論するのが一般的です。

Note

ユニオン型は、複数の型から成っていても、実行時には単一の型です。 つまり、ユニオン型がStringとInt32で構成されている場合、同時に両方になることはありません。 代わりに、StringかInt32のどちらかになります。

ユニオン型は、型をパイプ(|)で区切って宣言します。 多くの場合、括弧で囲みますが、必須ではありません。 最も一般的なユニオン型は(T | Nil)で、ここでTは型であり、Nilを返す可能性のあるメソッドで現れます。 これは、変数がnil許容であるとも言われます。

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

型は2つに限らず、いくつでも指定できます。

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof vs Object#class

変数の型を取得する方法は2つあります。 typeofを使うか、Object#classを使うかです。 違いは、typeofはコンパイル時に変数の型を返すのに対し、Object#classは実行時に型を返すことです。 つまり、例えば変数がユニオン型かどうかを確認したい場合、Object#classではそれができません。 実行時の型、つまり単一の型しか返さないからです。

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

ユニオン型の操作

ユニオン型は実行時には単一の型なので、標準的な操作はすべて使えます。 しかし、コードをコンパイルするとき、コンパイラーはそれがどの型であるかを知る必要があります。 そのため、型固有の操作を使いたいときは、コードがその時点でいずれか1つの型に定まるように書かれている必要があります。

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

Crystalには、ユニオン型のための特別なメソッドがあります。 is_a?メソッドです。 これは型を引数に取り、真偽値を返します。 nil?メソッドはis_a?(Nil)のショートカットです。 is_a?メソッドを制御式に書くと、コンパイラーにどの型であるかを伝え、それによってその型であることを保証します。 そしてelse分岐では、その型でないことが保証されます。

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

このis_a?は、引数に単一の型だけでなく、ユニオン型を取ることもできます。 また、複数の型に対して&&と組み合わせることもできます。

Note

is_a?メソッドは、制御式と組み合わせて使う場合、インスタンス変数やクラス変数にすることはできません。 代わりに、まずローカル変数に代入する必要があります。

as

ユニオン型を単一の型にする方法の1つは、特定の型である場合にのみ分岐に入るようにすることです。 もう1つの方法は、asメソッドを使うことです。 これは実行時チェックを行うことで、ユニオン型を単一の型にします。 型が期待したものでない場合、例外が発生します。

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

この方法は、型が期待したものであると確信している場合、またはそうでない場合に例外を発生させたい場合にのみ使うことを意図しています。

不適切な設定でこの方法を使うと、予期しない動作を引き起こす可能性があります。

as?

as?はasと非常に似ていますが、型が期待したものでない場合、例外を発生させる代わりにnilを返します。 つまり、期待した型とNilのユニオン型を返します。

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

nil許容の省略記法

nil許容とは、変数が型かNilのどちらかになりうることを意味します。 これは(T | Nil)と書けます。 しかし、nil許容型は比較的一般的なので、省略記法があります。 T?です。

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

ユニオン型の推論

コンパイラーは、どの型か確信が持てない場合、ユニオン型を推論します。 例えば、次のコードでは、fooはStringかInt32のどちらかになりうるため、コンパイラーはfooがどの型かわかりません。

if true
  foo = 1
else
  foo = "Hello"
end

typeof(foo) # => (Int32 | String)

この推論は自動的に行われ、他にも、例えばコンパイラーがメソッドがユニオン型を返すと推論するシナリオがあります。

character = "Hello world"[10]?
typeof(character) # => (Char | Nil)
GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

ユニオン型を学習する

練習はロックされています

ユニオン型を練習するには、あと1個の演習のロックを解除してください