Scala言語仕様 2.8 リーディング - 3 型
Scala言語仕様
日本語訳(Scala 2.8)
http://www.scala-lang.org/docu/files/LangSpec2.8-ja_JP.pdf
原文(Scala 2.9)
http://www.scala-lang.org/docu/files/ScalaReference.pdf
「<:」「>:」誤変換を防ぐために全角になっています
本当は避けたいところですが、はてな記法の解釈の挙動からやむをえず全角で表記しています。
導入
- 1階の型(first-order type)
- 値型(value type)は1階の型に含む
- 型パラメータを受け取って型を返す型コンストラクタ(type constructor)
値型
具象な値型(concrete value type):
- クラス、トレイトを参照する型指定子(type designator@3.2.3)
- 型の論理積(intersection)を表す複合型(compound type@3.2.7)
抽象な値型(abstract value type):
- 型パラメータ(@4.4)と抽象型束縛(abstract type bindings@4.3)によって導入
丸括弧によるグルーピング?中置型のところで出てきます。
非値型(non-value type)は、値ではない識別子のプロパティをとりこみ?@3.3
3.1 パス(Paths)
パスは型ではない、パスは名前つきの型(named type)の一部。
パッケージ名だったり、メンバー参照のために指定するsuperやthisなど。
- 空のパス(ユーザープログラムでは明示的に書けない)
- p.xのpはパスでxはpの安定メンバー(stable member)
- C.super.xのCはクラス参照、xはスーパークラスの安定メンバー(stable member)
- C.super[M].xのCはクラス参照、xはCの指定された親クラスMの安定メンバー(stable member)
安定メンバー(stable member):
オブジェクト定義 or 非volatile型の値定義に寄って導入されたメンバーやパッケージ
安定識別子(stable identifier):
最後が識別子で終わるパス
具体例。パッケージ名だったり、メンバー参照のために指定するsuperやthisなど。
class Example { // Example.superがパス、toStringが安定メンバー println("Example.super:" + Example.super.toString()) // superがパス、toStringが安定メンバー println("super:" + super.toString()) // Example.thisがパス、toStringが安定メンバー println("Example.this:" + Example.this.toString()) // thisがパス、toStringが安定メンバー println("this:" + this.toString()) } val e = new Example // 以下はREPLでは実行できない // com.exampleがパスでFooが安定メンバー package com.example { class Foo } val foo = new com.example.Foo
3.2 値型(Value Types)
- 3.2.1 シングルトン型(Singleton Types)
- 3.2.2 型射影(Type Projection)
- 3.2.3 型指定子(Type Designators)
- 3.2.4 パラメータ化された型(parameterized Types)
- 3.2.5 タプル型(Tuple Types)
- 3.2.6 アノテーション型(Annotated Types)
- 3.2.7 複合型(Compound Types)
- 3.2.8 中置型(Infix Types)
- 3.2.9 関数型(Function Types)
- 3.2.10 存在型(Existential Types)
3.2.1 シングルトン型(Singleton Types)
シングルトン型はp.typeの形の型、pがパスでscala.AnyRefに適合することがexpectされる値。
- null
- pで示される値
安定型(stable type):
シングルトン型 or scala.Singletonトレイトのsub typeであると宣言された型
scala> object SimpleType defined module SimpleType scala> SimpleType.type <console>:1: error: identifier expected but 'type' found. SimpleType.type ^ scala> SimpleType res0: SimpleType.type = SimpleType$@69ddad02
3.2.2 型射影(Type Projection)
T#xは、型Tのxという名前の型メンバーを参照する。
trait TP { type Projected } class StringTP extends TP { type Projected = String } class Example[ConcreteTP <: TP] { def p(v: ConcreteTP#Projected) = println(v) } val example = new Example[StringTP] example.p("fooo") // -> "fooo" example.p(123) /* scala> example.p(123) <console>:12: error: type mismatch; found : Int(123) required: StringTP#Projected example.p(123) ^ */
3.2.3 型指定子(Type Designators)
- 名前付きの値型(単純名 or qualified)を参照
- 型射影の略記表現
限定修飾とある箇所は原文で「qualified」。Intが単純名でscala.Intがqualified。
例えばIntやscala.Intと型を指定することは以下のような型射影の略記表現ということ。
Int scala.type#Int scala.Int scala.type#Int data.maintable.Node data.maintable.type#Node
3.2.4 パラメータ化された型(parameterized Types)
T[U1,...,Un]
T:型指定子(3.2.3)
U1,...,Un:型パラメータ
下限境界(L1,...,Ln)、上限境界(U1,...,Un)があれば、それに適合する必要がある。
- 上限境界(upper bound)
class Foo[T <: Pa] val f = new Foo[GrandPa] // NG val f: Foo[Pa] = new Foo[Pa] val f: Foo[Child] = new Foo[Child]
- 下限境界(lower bound)
class Foo[T >: Pa] val f: Foo[GrandPa] = new Foo[GrandPa] val f: Foo[Pa] = new Foo[Pa] val f = new Foo[Child] // NG
上限境界の例。
class ComparableAndAny[A <: Comparable[A], B] abstract class C extends Comparable[C] val caa = new ComparableAndAny[C, String]
型コンストラクタの例。
class F[M[_]] val f = new F[List]
3.2.5 タプル型(Tuple Types)
scala.Tuplen[T1,...,Tn]のエイリアス。
_1,...,_nのセレクタでフィールドにアクセス。
Productトレイトにまとめられている(=Tuple2はProduct2のサブ型)。
case class Tuple2 [+T1, +T2] (_1: T1, _2: T2) extends Product2[T1, T2] with Product with Serializable
http://www.scala-lang.org/api/2.9.1/index.html#scala.Tuple2
http://www.scala-lang.org/api/2.9.1/index.html#scala.Product2
3.2.6 アノテーション型(Annotated Types)
型の後ろにつけるアノテーションのこと。型に付与するアノテーション。
たとえば以下の例のUnitについている@suspendableがこれに該当。
(継続はよくわかってないですが、ここではannotated typeの使用例として)
http://jim-mcbeath.blogspot.com/2010/09/scala-coroutines.html
import scala.util.continuations._ trait Blocker { def waitUntilNotBlocked: Unit @suspendable = { ... } }
3.2.7 複合型(Compound Types)
- T1 with ... with Tn { R }
- 構成要素の型T1,...,Tnと細別(refinement){R}で与えられたメンバー
以下の例だとcloneAndResetの引数で指定している「Cloneable with Resetable」の型。
Cloneableのサブ型であり、かつResetableをミックスインしていること。
trait Cloneable extends java.lang.Cloneable { override def clone(): Cloneable = { super.clone(); this } } trait Resetable { def reset: Unit } def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { val cloned = obj.clone() obj.reset cloned }
withで複数の型を指定するだけでなく、細別(refinement)によってメンバーを与えることもできます。
def cloneAndReset(obj: Cloneable with Resetable { def print(): Unit }): Cloneable = { obj.print() val cloned = obj.clone() obj.reset cloned }
細別だけでも可。
def say(obj: { def toString(): String }): Unit = println(obj.toString())
3.2.8 中置型(Infix Types)
- T1 op T2で op[T1, T2]という型と等価
- *は反復パラメータ型(4.6.2)のために予約されているがそれ以外の任意の識別子が可能
以下の例でt2のような書き方ができるもの。
val t0: (String, Int) = ("a", 1) val t1: Pair[String, Int] = ("a", 1) val t2: String Pair Int = ("a", 1) scala> val t0: (String, Int) = ("a", 1) t0: (String, Int) = (a,1) scala> val t1: Pair[String, Int] = ("a", 1) t1: (String, Int) = (a,1) scala> val t2: String Pair Int = ("a", 1) t2: (String, Int) = (a,1)
丸括弧でグルーピングして、組み合わせることもできる。
val mp: Map[String, Pair[String, Int]] = Map("a" -> ("b", 1)) val mp: String Map (String Pair Int) = Map("a" -> ("b", 1)) scala> val mp: Map[String, Pair[String, Int]] = Map("a" -> ("b", 1)) mp: Map[String,(String, Int)] = Map(a -> (b,1)) scala> val mp: String Map (String Pair Int) = Map("a" -> ("b", 1)) mp: Map[String,(String, Int)] = Map(a -> (b,1))
といってもPairが何か特別な魔法のかかった型という訳ではなく、二つの型パラメータを受け取るものであればできる様子。
scala> class InfixOp[A,B] defined class InfixOp scala> val io: InfixOp[String, Int] = new InfixOp[String, Int] io: InfixOp[String,Int] = InfixOp@5f73089d scala> val io: String InfixOp Int = new InfixOp[String, Int] io: InfixOp[String,Int] = InfixOp@766f3870
参考:
http://jim-mcbeath.blogspot.com/2008/11/scala-type-infix-operators.html
3.2.9 関数型(Function Types)
- (T1,...,Tn) => U
- 引数型は反変、結果型は共変
反変(contravariant)
class Foo[-T] val f: Foo[Pa] = new Foo[GrandPa] val f: Foo[Pa] = new Foo[Pa] val f: Foo[Pa] = new Foo[Child] // NG
共変(covariant)
class Foo[+T] val f: Foo[Pa] = new Foo[GrandPa] // NG val f: Foo[Pa] = new Foo[Pa] val f: Foo[Pa] = new Foo[Child]
引数が反変?これは以下のような例で理解できる。
val f: (AnyRef => Int) = x => x.hashCode val g: (String => Int) = f
もし引数型が共変だと以下が許されてしまうのでおかしくなる。
val f: (AnyRef => Int) = (x: String) => x.hashCode
参考:
http://www29.atwiki.jp/tmiya/pages/52.html
実行時に関数を呼び出すときに渡すパラメータの型が反変なわけではない。
class GrandPa class Pa extends GrandPa class Child extends Pa def foo(p: Pa): GrandPa = p scala> foo(new GrandPa) <console>:12: error: type mismatch; found : GrandPa required: Pa foo(new GrandPa) ^ scala> foo(new Pa) res2: Pa = Pa@3394214b scala> foo(new Child) res3: Pa = Child@58046530
3.2.10 存在型(Existential Types)
- T forSome { Q }、Qは型宣言の並び
- T forSome { Q } forSome { Q' }はT forSome { Q; Q' }と書ける
- T forSome { Q; Q' }のQ'は使われていないので外せる
- T forSome {}はTに等価(=forSome {}は外せる)
Qを存在量化(quantification)と呼ぶ。
値の存在量化(Existential Quantification over Values):
http://stackoverflow.com/questions/2191142/existential-quantification-over-values
scala> class Graph { class Node } defined class Graph scala> val g = new Graph g: Graph = Graph@e9a555 scala> val n = new g.Node n: g.Node = Graph$Node@600a08 scala> def acceptNode[N <: g.Node forSome { val g: Graph }](n: N) = println(n) acceptNode: [N <: g.Node forSome { val g: Graph }](n: N)Unit scala> acceptNode(n) $line19.$read$$iw$$iw$Graph$Node@600a08
プレースホルダ構文:
「T forSome { type T }」の略記法
scala> def count(l: List[_]) = l.size count: (List[_])Int scala> def count(l: List[T forSome { type T }]) = l.size count: (List[T forSome { type T }])Int
このワイルドカード型は「_ >: L <: U」のLもUもないことから「_ >: Nothing <: Any」のように表現できる。
3.3 非値型(Non-Value Types)
プログラム中に明示的に表記されるものではない。定義された識別子(Indentifier)の内部的な型。
- 3.3.1 メソッド型(Method Types)
- 3.3.2 多相的メソッド型(Polymorphic Method Types)
- 3.3.3 型コンストラクタ(Type Constructors)
3.3.1 メソッド型
- 内部的には (Ps)U と表現される
- (Ps) :パラメータ名と型の並び、(p1: T1,...,pn: Tn)
- U:値型(value type) or メソッド型(method type)
- メソッド型は右結合(associate to the right)
- (Ps1)(Ps2)U == (Ps1)((Ps2)U)
右結合の類似として、コップ本第一版 $22.1.4よりコンスの右結合の説明を引用。
リストを構築する :: や ::: メソッドは特殊である。 というのも、それらのメソッド名は最後がコロンなので、右被演算子に束縛される。 つまり、 x :: xs のような演算は、 x.::(xs) ではなく、 xs.::(x) として扱われる。 実際、 x は単なるリストの要素型であって何でもよいのだから、その型が必ず :: メソッドを持っていると考えることはできない。 ゆえに、 x::(xs) では意味をなさないのだろう。
値の型としては存在しない、メソッド名が値として扱われる場合、暗黙に対応する関数型(3.2.9)に変換される。
scala> def a(x: Int)(y: String, z: String): String = (y+z)*x a: (x: Int)(y: String, z: String)String scala> val af = a _ af: Int => (String, String) => String = <function1>
3.3.2 多相的メソッド型(Polymorphic Method Types)
3.3.1 メソッド型に型パラメータを付与したもの。
- 内部的には[tps]T として表現される
- [tps]:型パラメータ部、[a1 >: L1 <: U1,...,an >: Ln <: Un]
- T:値型(value type) or メソッド型(method type)
def empty[A]: List[A] def union[A <: Comparable[A]] (x: Set[A], xs: Set[A]): Set[A]
これは以下のような型付け。下限境界のデフォルトはNothing型、上限境界のデフォルトはAny型。
empty : [A >: Nothing <: Any] List[A] // [tps] : [A >: Nothing <: Any] // T : List[A]
union : [A >: Nothing <: Comparable[A]] (x: Set[A], xs: Set[A]) Set[A] // [tps] : [A >: Nothing <: Comparable[A]] // T : (x: Set[A], xs: Set[A]) Set[A]
Nothingは型システムのBottomなので、全ての型はNothingと反変(contravariant)の関係。
http://www.scala-lang.org/api/current/index.html#scala.Nothing
3.3.3 型コンストラクタ(Type Constructors)
- 内部的には多相的メソッド型とほとんど同じように表現される
- [± a1 >: L1 <: U1,...,± an >: Ln <: Un] T
この例だとIterableが型コンストラクタ。
trait Iterable[+X] { def flatMap[newType[+X] <: Iterable[X], S](f: X => newType[S]): newType[S] }
3.4 基本型とメンバー定義 (Base Types and Member Definitions)
クラスのメンバの型はその参照方法に依存する。以下の3つの主要な概念がある。
- 型 T の基本型(base type)の集合
- ある前置型 S からみた、あるクラス C 中の、型 T
- ある型 T のメンバー束縛の集合
これらは相互に再帰的に定義される。
型 T の基本型(base types)の集合
TODO
ある前置型(prefix type) S からみた、あるクラス C 中の、型 T
TODO
ある型 T のメンバー束縛の集合
TODO
3.5 型の関係
- 等価:「T ≡ U」
T と U はあらゆるコンテキストにおいて交換可能
- 適合:「T <: U」
型 T は型 U に適合します
- 等価(T ≡ U) ∈ 適合(T <: U)
- 3.5.1 型の等価性(Type Equivalence)
- 3.5.2 適合性(Conformance)
- 3.5.3 弱い適合性(Weak Conformance)
3.5.1 型の等価性(Type Equivalence)
以下の場合、型は等価(≡)。
- type t = T で型エイリアスで定義されている場合 t ≡ T
- パス p がシングルトン型 q.type を持つ場合 p.type ≡ q.type
- o がobject定義で p が package または object selector だけからなり o の中で終わるパスの場合 o.this.type ≡ p.type?
- 共に複合型(compound type)で、構成要素の並びが対で等価かつ同じ順序、細別(refinement)も等価な場合
- 共にメソッド型で、等価な結果型、対で等価な型のパラメータを同じ数持っている場合(パラメータの名前は不問)
- 共に多相的メソッド型で、同じ数の型パラメータを持ち、(もし型パラメータが型エイリアスになっていても)その結果型が等価、対の型パラメータの上下限境界が等価な場合
- 共に存在型で、同じ数の存在量化子をもち、(もし存在量化子がエイリアスになっていても)存在量化された値が等価、対の存在量化子の上下限境界が等価な場合
- 共に型コンストラクタで、同じ数の型パラメータを持ち、(もし型エイリアスになっていても)型パラメータの上下限境界や変位指定と同様に結果型も等価な場合
3.5.2 適合性(Conformance)
以下の場合、型は適合する(<:)。
- T ≡ U の場合に T <: U (TはUに適合する)
- 値型 T は scala.Nothing <: T <: scala.Any
- 型コンストラクタ T は scala.Nothing <: T <: scala.Any
- T <: scala.AnyRef で T <: scala.NotNull ではない T は scala.Null <: T
- 型変数? or 抽象型である t は、t <: その上限境界、その下限境界 <: t
- クラス型 or パラメータ化された型 <: その基本型(base-types)
- p.type <: パス p の型
- p.type <: scala.Singleton
- もし T <: U なら 型射影 T#t <: U#t
- 全ての型パラメータが以下の3つの条件を満たすとき、T[T1,...,Tn] <: T[U1,...,Un]
- T の i 番目の型パラメータが共変(covariant)と宣言されているときに Ti <: Ui
- T の i 番目の型パラメータが反変(contravariant)と宣言されているときに Ui <: Ti
- T の i 番目の型パラメータが共変でも反変でもないと宣言されているときに Ti ≡ Ui
- 複合型 T1 with ... with Tn {R} <: Ti それぞれ
- T <: Ui(i=1,...,n) かつ R 中の型/値のあらゆる束縛を包含するメンバ束縛が T に存在すれば、 T <: U1 with ... with Un {R}
- 存在型 T forSome { Q } はそのskolemization が U に適合するなら T <: U
- T が U forSome { Q } の型インスタンスの一つに適合するなら T <: U forSome { Q }
- Ti ≡ T'i かつ U <: U' の場合、メソッド型(p1: T1,..., pn: Pn)U <: (p'1: T'1,...,p'n: T'n)U'
- 多相的メソッド型、T <: T' かつ Li <: L'i かつ U'i <: Ui なら [a1 >: L1 <: U1,...,an >: Ln <: Un]T <: [a1 >: L'1 <: U'1,...,an >: L'n <: U'n]T'
- 型コンストラクタ、T[a1,...,an] <: T'[a'1,...,a'n] なら T <: T'
- ai の境界は a'i に宣言された境界よりも弱い必要がある(弱いとは?)
- ai の変位指定は a'i の変位指定と一致する必要がある(共変なら共変、反変なら反変、それ以外なら不変)
- これらの制限は対になった ai と a'i の高階な型パラメータ節に再帰的に適用される
以下のいずれを満たす場合、複合型中の宣言/定義は 他の型や複合型中の同名の宣言を包含(subsume)する。
- T <: T' のとき、型 T をもつ名前 x を定義する値の宣言/定義は T' をもつ x を定義する値/メソッド宣言を包含する
- T <: T' のとき、型 T をもつ名前 x を定義するメソッド宣言/定義は T' をもつ x を定義するメソッド宣言を包含する
- T ≡ T' のとき、型エイリアス type t[T1,...,Tn] = T は type t[T1,...,Tn] = T' を包含する
- L' <: L かつ U <: U' のとき、型宣言 type t[T1,...,Tn] >: L <: U は type t[T1,...,Tn] >: L' <: U' を包含する
- L <: t <: U のとき、型名 t を束縛する型/クラス定義は抽象型宣言 type t[T1,...,Tn] >: L <: U を包含する
適合の関係は型の間に前秩序(pre-order)を形成する。
推移律(transitive)と反射律(reflexive)を満たす。
型の集合の最小の上限境界、最大の下限境界はこの順序に関して関連がある(relative)。
- 前秩序(pre-order)
集合 A 上の(半)順序(関係) (partial order) とは、 反射的 (reflexive) で反対称的 (antisymmetric) かつ推移的 (transitive) な A 上の二項関係すなわち a, b, c を A の任意の元として 反射律: a 〜 a, 推移律: a 〜 b かつ b 〜 c ならば a 〜 c, 反対称律: a 〜 b かつ b 〜 a ならば a = b が成立するような関係 "〜" のことをいう。 反射律と推移律が成り立つ二項関係は前順序(preorder)と呼ばれる。
http://ja.wikipedia.org/wiki/%E9%A0%86%E5%BA%8F%E9%9B%86%E5%90%88
※はてなで表記できない文字を置換しています
3.5.3 弱い適合性(Weak Conformance)
- S <:w T : SはTに弱く適合する
弱く適合する条件:
- S <: T の場合
- S と T 共に数値型で、以下の順序中 S が T より前にある場合
Byte <:w Short Short <:w Int Char <:w Int Int <:w Long Long <:w Float Float <:w Double
弱い最小の上限境界(weak least upper bound):弱い適合性に関する最小の上限境界。
3.6 volatile 型(Volatile Types)
型の揮発性(volatility):
型の抽象型インスタンスあるいは型パラメータがいかなる非 null 値も持たない可能性
「3.1 で説明したようにvolatile型の値メンバーはパス中に現れることができない」とはstable memberが非volatileである必要があることを指す?
(再掲)安定メンバー(stable member):オブジェクト定義 or 非volatile型の値定義に寄って導入されたメンバーやパッケージ
以下の条件を満たす場合、その型はvolatileである。
- 4つのカテゴリ(?)の一つに当てはまる
T1 with ... with Tn {R} はいずれかの条件を満たす場合、volatileである。
- T2,...,Tn の一つが型パラメータ or 抽象型
- T1 がabstract type、かつ、細別 R or 型 Tj(j>1) が複合型にabstractなメンバを提供している
- T1,..,Tn のいずれかがシングルトン型である
- 型指定子がvolatile な型のエイリアス or 上限境界として volatile な型を持つ型パラメータ or 抽象型 を指定する場合その型指定子は volatile
- パス p の 内在する型?が volatile なら p.type は volatile
- 存在型 T forSome {Q} は T が volatile なら volatile
3.7 型消去(Type Erasure)
- |T| : T の型消去
型消去のマッピングの定義は以下の通り。
- 型エイリアスの型消去は、その右側の型の型消去になる
- 抽象型(abstract type)の型消去は、その上限境界の型消去になる
- scala.Array[T1] の型消去は scala.Array[|T1|]
- それ以外の T[T1,...,Tn] の型消去は |T|
- シングルトン型 p.type の型消去は p の型消去
- 型射影 T#x の型消去は |T|#x
- 複合型 T1 with ... with Tn {R} の型消去は T1,...,Tn の論理積ドミネータ(intersection dominator)の型消去
- 存在型 T forSome {Q} の型消去は |T|
ジェネリック:
型引数 or 型変数を含む型の性質
型消去(type erasure):
(ジェネリックでありうる)型から非ジェネリックな型へのマッピング
T1,...,Tn の論理積ドミネータ(intersection dominator)の計算:
・Ti1,...,Tim : 他の型 Tj の親の型ではない Ti の部分列(subsequence)
・この Ti1,...,Tim が(traitではなく)classを参照する型指定子 Tc を含む場合、論理積ドミネータは Tc となる
・そうでない場合は、論理積ドミネータは部分列の最初の要素 Ti1 となる
※複合型にはclassは一つ以下しか含まれず、他は全てtraitである点に注意