読者です 読者をやめる 読者になる 読者になる

seratch's weblog in Japanese

About Scala, Java and Ruby programming in Japaense. If you need English information, go to http://blog.seratch.net/

Scala言語仕様 2.8 リーディング - 3 型

これはAkasaka.scala読書会の記録です

これはAkasaka.scalaでの読書会の記録です。私が事前にある程度準備しておいて、読書会当日に修正・追記を行います。

http://akskscala.github.com/

「<:」「>:」誤変換を防ぐために全角になっています

本当は避けたいところですが、はてな記法の解釈の挙動からやむをえず全角で表記しています。

導入

  • 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())

参考:
http://www.scala-lang.org/node/110

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である点に注意