存在型
∀に対応する全称型があるならば、∃に対応する存在型があると考えるのが自然です。 存在型は難しいものではありません。そうと意識していないだけで、既にあなたは存在型を知っています。
T: Trait
f x: T = ...
上のトレイトT
は存在型として使われています。
対して下の場合のT
はトレイトでしかなく、X
は全称型です。
f|X <: T| x: X = ...
実際、存在型は全称型に置き換えられます。ではなぜ存在型などというものが存在するのでしょうか。 まず、上で見たように存在型は型変数を伴わないので、型指定をシンプルにできます。 また、型変数を除去できるので全称型ならランク2を超えてしまうような型も構成できます。
show_map f: (|T| T -> T), arr: [Show; _] =
arr.map x ->
y = f x
log y
y
しかし、見ればわかるように存在型は元の型を忘却・拡大してしまうので、戻り値の型を広げたくない場合などは全称型を使う必要があります。 逆に、引数として受け取るだけで戻り値に関係のない型は存在型で記述して構いません。
# id(1): Intが期待される
id|T|(x: T): T = x
# |S <: Show|(s: S) -> ()は冗長
show(s: Show): () = log s
ちなみに、クラスは存在型とは呼びません。予めその要素となるオブジェクトが定められているためです。 存在型はあるトレイトを満たすすべての型という意味で、実際にどのような型が代入されるか知るところではないのです。