前置き
カインドの説明に関してはありませんので、「カインドとは何?」ということを知りたい人のお役には立てません。
この記事で調べたこと
調べたかったことは、「Upper BoundやLower Boundがある、Scalaの型システムの中で、どのようにカインドが表現されるか」です。
値に関する制約は型で表現し、
型に関する制約はカインドで表現する
だと思うので、Upper BoundとLower Boundという、型に関する制約はカインドに現れるはずだと思いました。
(ここで言う制約というのが意味しているのは、Int
という型は1
, -2
, 18
などの値は受け付けるが、"hello"
, 'c'
, true
などの値は受け付けないという、値に対して制約があるという意味で使ってます。カインドで言えば、*
というカインドはInt
, Char
, List[Double]
という型は受け付けるが、List
やSet
のようは*
でないものを受け付けない型に対する制約がある、という意味です。)
scalaのバージョン
Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_65).
Boundの指定なし
まずは、Boundの指定なしのカインドを見てみます
以下のA
にはどんな型でのパラメータとし取れるので、A[Int]
でもA[String]
でもA[List[Char]
でもカインドが*
ならなんでもOKです。
class A[T1]
よって、:k -v A[_]
してみると、以下のように* -> *
だということがわかります。
scala> :k -v A[_]
A's kind is F[A]
* -> *
This is a type constructor: a 1st-order-kinded type.
Upper Bound指定
以下のようなB
だと、今さっきのように、B[Int]
, B[List[Char]]
は受けません。
class B[T1 <: CharSequence]
B
はCharSequence
をUpper Boundに持つので、以下のような指定なら可能です。
B[String]
B[StringBuilder]
B[StringBuffer]
どんな型でも受け付けるわけではないから、B
のカインドはただの* -> *
ではだめのはずです。最初の型引数の*
のには何らかの制約を表す記法を導入しているはずです。
以下が:k -v B[_]
の結果です。
scala> :k -v B[_]
B's kind is F[T1 <: CharSequence]
*(CharSequence) -> *
This is a type constructor: a 1st-order-kinded type.
これで、B
のカインドは*(CharSequence) -> *
と表すということがわかりました。
ここからUpper Boundは、*(CharSequence)
とScalaの世界では表すだということがわかりました。では、Lower Boundだとどういう記法でカインドを表すのでしょうか?
Lower Boundの指定
以下ようなC
だと、Int
より上の型しか受けません。そのため、C[String]
とかC[Double]
とかはダメです。
class C[T1 >: Int]
妥当なものは、以下のようものたちです。
C[Int]
C[AnyVal]
C[Any]
以下は:k -v C[_]
の結果です。
scala> :k -v C[_]
C's kind is F[T1 >: Int]
*(Int, Any) -> *
This is a type constructor: a 1st-order-kinded type.
これで、C
のカインドは*(Int, Any) -> *
だということがわかりました。
* (Int, Any)
という風にAny
が出てきてしまっているのは、自動的に上はAny
以上ないので、Upper Boundが出てきてしまっているのでしょう。(じゃあ、なぜ今さっきのB
が*(Nothing, CharSequece) -> *
にならなかったのか不思議...)
だいたい想像できてしまっていますが、Lower BoundとUpper Boundを同時に指定したらどうなるでしょうか?
Lower, Upper両方ともを指定
以下がクラス定義です。
class D[T1 >: String <: AnyRef]
scala> :k -v D[_]
D's kind is F[T1 >: String <: AnyRef]
*(String, AnyRef) -> *
This is a type constructor: a 1st-order-kinded type.
これで、*(String, AnyRef) -> *
だとわかりました。やはり*(Lower, Upper)
と表すのがScalaでの表現のようです。
まとめ
- Upperだけの指定だと
*(Upper)
- Lowerだけの指定だと
*(Lower, Any)
- 両方とも指定すると、
* (Lower, Upper)
おまけ - Haskellで型クラスで型パラメータに制約をつける
一応Haskell(GHC)なら、Lower, Upper制限ではないですが、制約つきの型パラメータのある型は作ることできます。その型パラメータのカインドを見てみたいと思います。
ghci
で以下のように実行してみました。
DatatypeContexts
言語拡張を使って、A
型パラメータa
にはOrd
型クラスのインスタンスではないといけないという制約をつけました。
Prelude> :set -XDatatypeContexts
Prelude> data Ord a => A a = AConstructor a
Prelude> :k A
A :: * -> *
型クラス制約はカインドでは現れないみたいです。そのため、OrdインスタンスではないIO ()
型使って以下のようなこともできてしまいました。
type X = A (IO ())
しかし、AConstructor (print "hello")
ようにすると以下のエラーがでます
No instance for (Ord (IO ())) arising from a use of ‘AConstructor’
なんとなく想像ですが、データ構築子のAConstructor
にOrd
の制約ついているだけで、型構築子A
にはOrd
の制約がついていない気がします。
あと、まだScalaでカインドについてやれることで思いつくことは、「共変・反変」だと思います。この記事では非変な型パラメータしかやっていないので、A[+T]
とかB[-T]
とかがどのようなカインドになるか?...