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

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 リーディング - 4 基本的な定義と宣言

Scala Akasaka.scala

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

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

http://akskscala.github.com/

目次

  • 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 が(この定義以外の箇所で)はじめてアクセスされたとき、上記と同様となる
定数値の定義(constant value definition)

val と final val の違いについて。

  • 「final val x = e」のとき式 e は定数式(6.24)であり、定数値 x への参照もまた定数式として扱われる

定数式とは?6.24より引用。

定数式は Scala コンパイラが定数へ評価できる式で、その定義はプラットフォーム(JVM or .NET?)に依存する。

  • 値クラスのリテラル。一つの整数など。
  • 文字列リテラル
  • Prefdef.classOf で構築されるクラス(12.5)
  • 基盤となっているプラットフォームからの列挙要素(enum)。
  • Array(c1,...,cn) の形のリテラル配列。ここで ci はすべてそれ自身が定数式。
  • 定数値定義によって定義された識別子。

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.Seq[T] *1 型として扱われる

実際の実装クラスは「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

*1:原文ではscala.Seq[T]とあるがパッケージ誤り