LoginSignup
3
1

More than 5 years have passed since last update.

Scalaにおけるカインド(Kind) - Upper Bound, Lower Boundでのカインドを調べてみる

Last updated at Posted at 2017-09-07

前置き

カインドの説明に関してはありませんので、「カインドとは何?」ということを知りたい人のお役には立てません。

この記事で調べたこと

調べたかったことは、「Upper BoundやLower Boundがある、Scalaの型システムの中で、どのようにカインドが表現されるか」です。

に関する制約はで表現し、
に関する制約はカインドで表現する

だと思うので、Upper BoundとLower Boundという、型に関する制約はカインドに現れるはずだと思いました。

(ここで言う制約というのが意味しているのは、Intという型は1, -2, 18などの値は受け付けるが、"hello", 'c', trueなどの値は受け付けないという、値に対して制約があるという意味で使ってます。カインドで言えば、*というカインドはInt, Char, List[Double]という型は受け付けるが、ListSetのようは*でないものを受け付けない型に対する制約がある、という意味です。)

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]

BCharSequenceを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’

なんとなく想像ですが、データ構築子のAConstructorOrdの制約ついているだけで、型構築子AにはOrdの制約がついていない気がします。


あと、まだScalaでカインドについてやれることで思いつくことは、「共変・反変」だと思います。この記事では非変な型パラメータしかやっていないので、A[+T]とかB[-T]とかがどのようなカインドになるか?...

参考

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1