Scala言語仕様 2.8 リーディング - 4 基本的な定義と宣言
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
目次
- 4.1 値宣言と定義(Value Declarations and Definitions)
- 4.2 変数宣言と定義(Variable Declarations and Definitions)
- 4.3 型宣言と型エイリアス (Type Declarations and Type Aliases)
- 4.4 型パラメータ (Type Parameters)
- 4.5 変位指定アノテーション (Variance Annotations
- 4.6 関数宣言と定義(Function Declarations and Definitions)
- 4.6.1 名前呼び出しパラメータ (By-Name Parameters)
- 4.6.2 反復パラメータ (Repeated Parameters)
- 4.6.3 手続き (Procedures)
- 4.6.4 メソッドの戻り値型の推論 (Method Return Type Inference
- 4.7 インポート節 (Import Clauses)
4.1 値宣言と定義(Value Declarations and Definitions)
Dcl ::= 'val' ValDcl ValDcl ::= ids ':' Type Def ::= 'val' PatDef PatDef ::= Pattern2 {',' Pattern2} [':' Type] '=' Expr // 8 パターンマッチ ids ::= id {',' id} // 1.1 識別子
値の宣言
値の宣言は「val x: T」のように名前 x に値が束縛されない。
- 「val x: T」は型 T の値の名前として x を導入する。
値の定義
値の定義では「val x: T = e」のように値の式 e の評価から得られる値が名前 x に束縛される。
- 「val x: T = e」は式 e の評価から得られる値の名前として x を定義する。
- この値定義が再帰的でないなら型 T は省略されることがあり、その場合は式 e のパックされた型?が想定される(assumed)。
- 型 T が明示されていれば式 e は T に適合することが要請される(expected)。
値の定義で左辺にパターンをとる
値の定義では、左辺にパターンをとることができる。
左辺にパターン p を、右辺に式 e をとった場合
val p = e
以下のように展開される。
p が束縛変数 x1,...,xn (n>1) を持つ場合:
val $x = e match { case p => {x1,...,xn} } val x1 = $x._1 ... val xn = $x._n
p がただ一つの束縛変数 x を持つ場合:
val x = e match { case p => x }
p が束縛変数を持たない場合:
e match { case p => () }
具体例を Example 4.1.1 より引用。
val Some(x) = Option("foo") val x :: xs = List(1,2,3)
scala> def f() = Option("foo") f: ()Option[java.lang.String] scala> val Some(x) = f() x: java.lang.String = foo scala> val x :: xs = List(1,2,3) x: Int = 1 xs: List[Int] = List(2, 3)
展開内容は以下の通り。
val x = Option("foo") match { case Some(x) => x } val x$ = List(1,2,3) match { case x :: xs => {x, xs} } val x = x$._1 val xs = x$._2
カンマ区切りで値の宣言・定義を並べる
カンマ区切りで同じ内容の宣言・定義を並べることができます。
値宣言 val x1,...,xn : T は、値宣言の並び val x1 : T ; ...; val xn : T の略記表現です。
値定義 val p1,...,pn : T = e は、値定義の並び val p1 : T = e ; ...; val pn : T = e の略記表現です。
scala> val x1, x2: String = "a" x1: String = a x2: String = a scala> val iter = List(1,2,3).iterator iter: Iterator[Int] = non-empty iterator scala> def v = iter.next v: Int scala> val x1, x2: Int = v x1: Int = 1 x2: Int = 2
遅延評価値の定義(lazy value definition)
val と lazy val の違いについて。
- lazy がなければ、右辺式 e の評価結果を型 T に変換した値に名前 x を束縛する
- lazy があれば、名前 x が(この定義以外の箇所で)はじめてアクセスされたとき、上記と同様となる
4.2 変数宣言と定義(Variable Declarations and Definitions)
Dcl ::= 'var' VarDcl Def ::= 'var' VarDef VarDcl ::= ids ':' Type VarDef ::= PatDef | ids ':' Type '=' '_'
変数の宣言
変数の宣言「var x: T」は以下のようにgetter/setterを定義することと等価。
def x: T def x_=(y: T): Unit
変数の定義
- 「var x: T = e」は式 e の評価から得られる値を初期値とする 型が T で mutable な変数 x を導入する。
- 型 T は省略されることがあり、その場合は式 e の型が想定される(assumed)。
- 型 T が明示されていれば式 e は T に適合することが要請される(expected)。
変数の定義で左辺にパターンをとる
変数も値と同様に左辺にパターンをとることができる。
scala> var Some(x) = Option("foo") x: java.lang.String = foo scala> x = "bar" x: java.lang.String = bar
変数の定義でデフォルトの初期値を導入する
- 「val x: T = _」でデフォルトの初期値を導入する
scala> var x: Int = _ x: Int = 0 scala> val x: Int = _ <console>:1: error: unbound placeholder parameter val x: Int = _ ^ scala> var x: Long = _ x: Long = 0 scala> var x: Float = _ x: Float = 0.0 scala> var x: Double = _ x: Double = 0.0 scala> var x: Unit = _ x: Unit = () scala> var x: String = _ x: String = null scala> class Foo defined class Foo scala> var x: Foo = _ x: Foo = null
4.3 型宣言と型エイリアス (Type Declarations and Type Aliases)
Dcl ::= 'type' {nl} TypeDcl TypeDcl ::= id [TypeParamClause] ['>:' Type] ['<:' Type] Def ::= type {nl} TypeDef TypeDef ::= id [TypeParamClause] '=' Typ
- 型宣言「type t[tps] >: L
- 型パラメータ節 [tps] が省略された場合 t は一階型(first-order type)の抽象化を表す
- L は指定がなければ scala.Nothing、 U は指定がなければ scala.Any が想定される(assumed)
type IntList = List[Integer] type T <: Comparable[T] type Two[A] = Tuple2[A, A] type MyCollection[+X] <: Iterable[X]
4.4 型パラメータ (Type Parameters)
TypeParamClause ::= '[' VariantTypeParam {',' VariantTypeParam} ']' VariantTypeParam ::= {Annotation} ['+' | '-'] TypeParam TypeParam ::= (id | '_') [TypeParamClause] ['>:' Type] ['<:' Type] [':' Type]
型パラメータは class、関数の定義で現れる。
ここでは下限・上限境界をもつ型パラメータ定義のみを扱い、コンテキスト境界(「: U」)と可視境界(「<% U」)をもつものは 7.4 で扱う。
- 一階の型パラメータの最も一般的な形は「@a1 ... @an ± t >: L
- 「@a1 ... @an」はアノテーション
- 「±」は変位指定(variance)で「+」「-」も必須ではない前置詞
- 「>: L」は下限境界、「
- 全ての型パラメータの名前はパラメータ節の中で互いに異なっていなければならない
- 型パラメータのスコープはそれぞれ型パラメータ節全体となる
- ネストした型パラメータ節の一般的な形は「@a1 ... @an ± t[tps] >: L
[S, T]
[@specialized T, U]
[Ex <: Throwable]
[A <: Comparable[B], B <: A]
[A, B >: A, C >: A <: B]
[M[X], N[X]]
[M[_], N[_]] // 上の節に等価
[M[X <: Bound[X]], Bound[_]]
[M[+X] <: Iterable[X]]
4.5 変位指定アノテーション (Variance Annotations
変位には以下の3つがあります。
- +T: 共変(covariant):その型もしくはそのサブ型を受け入れる
- -T: 反変(contravariant):その型もしくはその親の型を受け入れる
- T: 非変(nonvariant):その型のみを受け入れる
変位指定のポジションとは?
- メソッドのパラメータでの変位指定のポジションは、とりかこむ(有効な?)型パラメータ節での変位指定のポジションとは反対(opposite)になる
わかりにくいところなので実際の例を確認。
Example 4.5.2
クラス宣言での型パラメータ A は共変として定義されているため、メソッドパラメータではその反対の反変であるべきところに A が現れるため不正。
scala> abstract class Sequence[+A] { def append(x: Sequence[A]): Sequence[A] } <console>:7: error: covariant type A occurs in contravariant position in type Sequence[A] of value x abstract class Sequence[+A] { def append(x: Sequence[A]): Sequence[A] } ^
下限境界を指定することで回避できる。
scala> abstract class Sequence[+A] { def append[B >: A](x: Sequence[B]): Sequence[B] } defined class Sequence
- mutable な変数の型は常に非変なポジションの中
これを示す例を確認。
Example 4.5.1
値であれば許容されるが、変数の場合は不正な変位となる例。
scala> abstract class P[+A, +B](a: A, b: B) { val _a: A = a; val _b: B = b } defined class P scala> abstract class P[+A, +B](a: A, b: B) { var _a: A = a; var _b: B = b } <console>:7: error: covariant type A occurs in contravariant position in type A of value _a_= abstract class P[+A, +B](a: A, b: B) { var _a: A = a; var _b: B = b } ^ // クラス内のみ公開でもダメ scala> abstract class P[+A, +B](a: A, b: B) { private var _a: A = a; private var _b: B = b } <console>:7: error: covariant type A occurs in contravariant position in type A of value _a_= abstract class P[+A, +B](a: A, b: B) { private var _a: A = a; private var _b: B = b } ^ // インスタンス内のみ公開なら OK scala> abstract class P[+A, +B](a: A, b: B) { private[this] var _a: A = a; private[this] var _b: B = b } defined class P
- 下限境界での変位指定のポジションは、型宣言 or 型パラメータの変位指定のポジションの反対になる
- 型選択 S#T の前置子 S は常に非変のポジションの中
- 型 S[... T ...] の型引数 T が非変なら非変なポジション、そうでなければ型 S[... T ...] を取り囲む変位指定のポジションの反対になる
4.6 関数宣言と定義(Function Declarations and Definitions)
Dcl ::= 'def' FunDcl FunDcl ::= FunSig ':' Type Def ::= 'def' FunDef FunDef ::= FunSig [':' Type] '=' Expr FunSig ::= id [FunTypeParamClause] ParamClauses FunTypeParamClause ::= '[' TypeParam {',' TypeParam} ']' ParamClauses ::= {ParamClause} [[nl] '(' 'implicit' Params ')'] ParamClause ::= [nl] '(' [Params] ')'} Params ::= Param {',' Param} Param ::= {Annotation} id [':' ParamType] ['=' Expr] ParamType ::= Type | '=>' Type | Type '*'
- 関数の宣言は「def f psig: T」の形、定義では「def f psig: T = e」で本体 e を含む
- psig はパラメータシグニチャ、T は結果の型
パラメータシグニチャ?
関数定義部分のうち「[FunTypeParamClause] ParamClauses」の部分のこと。
Def ::= 'def' FunDef FunDef ::= FunSig [':' Type] '=' Expr FunSig ::= id [FunTypeParamClause] ParamClauses
パラメータシグニチャは型パラメータ節「[tps]」(必須でない)とその後に続く0個以上の値パラメータ節「(ps1)..(psn)」から成る。
- 宣言時にパラメータ型と結果型が与えられたメソッド型を持つ値を導入する
3.3.1 メソッド型の説明から再掲。メソッド型は値の型でないため暗黙に関数型に変換されるものと思われる。
メソッド型は、値の型としては存在しません。 もしメソッド名が値として使われるなら、その型は暗黙のうちに対応する関数型に変換されます(§6.26)
- 関数本体 e の型は関数に宣言された結果型に適合することが要請(expect)される
- 関数定義が再帰的でなければ結果型は省略可。 その場合、結果型は関数本体のパックされた型(packed type)から決定?
- 型パラメータ節は1つ以上の型宣言から成る、スコープはシグニチャ全体
- メソッドパラメータのシングルトン型はメソッド本体の中にだけ現れることができる?
- 依存メソッド型(dependent method types)はサポートされていない(Scala 2.8 での話)
Scala 2.10 からサポートされる?
https://gist.github.com/1306328
4.6.1 名前呼び出しパラメータ (By-Name Parameters)
ParamType ::= '=>' Type
名前渡し。
- 「x : => T」パラメータなしのメソッド型「=> T」
- 関数適用時にはその引数は評価されない、関数内の使用の度に評価される
// 通常のメソッド val iter = (1 to 3).iterator def v() = iter.next def p3times(x: Int) = for (_ <- 1 to 3) println(x) p3times(v()) // 1 1 1 // 名前渡し val iter = (1 to 3).iterator def v() = iter.next def p3timesByName(x: => Int) = for (_ <- 1 to 3) println(x) p3timesByName(v()) // 1 2 3
4.6.2 反復パラメータ (Repeated Parameters)
ParamType ::= Type '*'
連続パラメータとも呼ばれる。Java でいう可変長引数。
実際の実装クラスは「scala.collection.mutable.WrappedArray」というクラスでこれが Seq のサブ型。
http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.WrappedArray
scala> def rp(name: String, xs: Int*) = println(xs) rp: (name: String, xs: Int*)Unit scala> rp("aaa",1,2,3,4,5) WrappedArray(1, 2, 3, 4, 5)
「(p1:T1,...,pn:Tn,ps:S*)U」をもつメソッド m が引数 (e1,...,ek) に適用されるなら(k >= n) m はその適用中に、型 S が k - n 個出現する型 (p1:T1,...,pn:Tn,ps:S,..., ps´S)U を持っていると解釈されます。
シーケンス引数(sequence argument)
最後の引数が「_*」型注釈を指定された「シーケンス引数(sequence argument)」として定義されている場合。
引数 (e1,...,en , e´ : _*) に適用されるなら、適用中の m の型は (p1 : T1,...,pn: Tn , ps :scala.Seq[S]) と解釈されます
scala> val xs = List(1,2,3) xs: List[Int] = List(1, 2, 3) scala> rp("aaa", xs) <console>:10: error: type mismatch; found : List[Int] required: Int rp("aaa", xs) ^ scala> rp("aaa", xs: _*) List(1, 2, 3) scala> rp("aaa", 0, xs: _*) <console>:10: error: no `: _*' annotation allowed here (such annotations are only allowed in arguments to *-parameters) rp("aaa", 0, xs: _*) ^
4.6.3 手続き (Procedures)
FunDcl ::= FunSig FunDef ::= FunSig [nl] '{' Block '}'
- Block の前に「=」を書かない
- 手続き宣言は、結果型が省略された関数宣言、暗黙に結果型は Unit 型になる
def echo(x: String): String = { println(x) x } def echo(x: String) { println(x) x }
Scala 2.9.1:
scala> def echo(x: String): String = { | println(x) | x | } echo: (x: String)String scala> def echo(x: String) { | println(x) | x | } echo: (x: String)Unit
Scala 2.10.0-M1:
Scala 2.10 から Unit 型を返すはずなのにブロックの最後の行で Unit 以外の型を返す式を評価している場合は warning が出るようになりそう。
scala> def echo(x: String): String = { | println(x) | x | } echo: (x: String)String scala> scala> def echo(x: String) { | println(x) | x | } <console>:9: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses x ^ echo: (x: String)Unit scala> def echo(x: String): Unit = { println(x); x } <console>:7: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses def echo(x: String): Unit = { println(x); x } ^ echo: (x: String)Unit
4.6.4 メソッドの戻り値型の推論 (Method Return Type Inference)
- オーバーライドしたメソッドはそれが再帰的であっても結果型を省略可能
- 親の結果型が適合していれば結果型の指定が異なっていてもよい
object ListUtil { def reverse[A](xs: A*): List[A] = xs.toList match { case Nil => Nil case y :: ys => reverse(ys: _*) ::: List(y) } } object ExtListUtil extends ListUtil { override def reverse[A](xs: A*) = super.reverse(xs: _*) } ExtListUtil.reverse(3,2,1)
4.7 インポート節 (Import Clauses)
Import ::= 'import' ImportExpr {',' ImportExpr} ImportExpr ::= StableId '.' (id | '_' | ImportSelectors) ImportSelectors ::= '{' {ImportSelector ','} (ImportSelector | '_') '}' ImportSelector ::= id ['=>' id | '=>' '_']
- 「import p.I」の形で p は安定識別子(3.1)、 I はインポート式
安定識別子?
最後が識別子で終わるパスのこと。
インポート式?
- 「{ x,y,z }」「{ x => xxx, y => _ }」「{ _ }」の組み合わせ
package foo { object X { val x = "xxx" val y = "yyy" } }
上記の「foo.X.x」を import するケースを考えてみる。
既に x をローカルに定義している状態で「foo.X.x」を導入しようとすると warning になる。
(結果としては warning メッセージの通り、ローカル定義の方が優先順位が上であるため、「foo.X.x」は無視される)
object Main0 extends App { val x = "expcted" import foo.X.x println(x) // "expected" } // foo.scala:10: warning: imported `x' is permanently hidden by definition of value x in object Main0 // import foo.X.x // ^ // one warning found
「foo.X.x」をワイルドカードにリネームして「x」という名前を import 対象から捨てるとワーニングは出力されない。
object Main1 extends App { val x = "expcted" import foo.X.{x => _ } println(x) // "expected" } object Main2 extends App { val x = "expcted" import foo.X.{x => _, _ } println(x) // "expected" println(y) // "yyy" }
ワイルドカードでの import は import 節の中での末尾である必要がある。
object Main3 extends App { val x = "expcted" import foo.X.{_, x => _ } println(x) } // foo.scala:16: error: Wildcard import must be in last position