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

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 リーディング - 6 式

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

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

http://akskscala.github.com/

目次

  • 6.1 式の型付け(Expression Typing)
  • 6.2 リテラル(Literals)
  • 6.3 null 値(The Null Value)
  • 6.4 指定子(Designaters)
  • 6.5 this と super(This and Super)
  • 6.6 関数適用(Function Applications)
    • 6.6.1 名前付き引数とデフォルト引数(Named and Default Arguments)
  • 6.7 メソッド値(Method Values)
  • 6.8 型適用(Type Applications)
  • 6.9 タプル(Tuples)
  • 6.10 インスタンス生成式(Instance Creation Expressions)
  • 6.11 ブロック(Blocks)
  • 6.12 前置、中置、後置演算(Prefix、Infix and Postfix Operations)
    • 6.12.1 前置演算(Prefix Operations)
    • 6.12.2 中置演算(Infix Operations)
    • 6.12.3 後置演算(Postfix Operations)
    • 6.12.4 代入演算子(Assignment Operators)
  • 6.13 型付けされた式(Typed Expressions)
  • 6.14 アノテーション(注釈)付きの式(Annotated Expressions)
  • 6.15 代入(Assignments)
  • 6.16 条件式(Conditional Expressions)
  • 6.17 while ループ式(While Loop Expressions)
  • 6.18 do ループ式(Do Loop Expressions)
  • 6.19 fo 内包表記と for ループ(For Comprehensions and For Loops)
  • 6.20 return 式(Return Expressions)
  • 6.21 throw 式(Throw Expressions)
  • 6.22 try 式(Try Expressions)
  • 6.23 無名関数(Anonymous Functions)
  • 6.24 定数式(Constant Expressions)
  • 6.25 文(Statements)
  • 6.26 暗黙の型変換(Implicit Conversions)
    • 6.26.1 値変換(Value Conversions)
    • 6.26.2 メソッド変換(Method Conversions)
    • 6.26.3 オーバーロード解決(Overloading Resolution)
    • 6.26.4 ローカルな型推論(Local Type Inference)
    • 6.26.5 イータ展開(Eta Expansion)

6.1 式の型付け(Expression Typing)

「式 e は型 T に適合することが要請される(expected)」という場合

  • 式 e の要請型(expected type)は型 T である
  • 式 e の型は型 T に適合しなければならない


3.2.10 からスコーレム化について再び引用。

T forSome { Q }の型インスタンスは、型 σT です。 ここで σは、各 i に対し σLi <: σti <: σUi であるような t1,...,tn の置換とします。 
存在型 T forSome { Q } によって示される値の集合は、そのすべての型インスタンスの値の集合の和集合です。
T forSome { Q } の skolemization は、型インスタンス σT です。ここで σは、[t´1/t1,...,t´n/tn] の置換で、各 t´i は下限境界 σLi と上限境界 σUi をもつ新規の抽象型です。

型 T の式 e があって「t1[tps1] >: L1 <: U1,...,tn[tpsn] >: Ln <: Un」は T 中で自由な e のある部分のスコーレム化によって生成されたすべての型変数であるとき、式 e の「パックされた型」は「T forSome { type t1[tps1] >: L1 <: U1 ; ...; type tn[tpsn] >: Ln

6.2 リテラル(Literals)

SimpleExpr  ::=   Literal

6.3 null 値(The Null Value)

null は scala.Null 型で全ての参照型と互換

Null is - together with Nothing - at the bottom of the Scala type hierarchy.

http://www.scala-lang.org/api/current/index.html#scala.Null

scala> val x: String = null
x: String = null

scala> val z: String = null
z: String = null

// null 同士なら true
scala> z.eq(x)
res3: Boolean = true

scala> z == x
res4: Boolean = true

// null 同士でなければ false
scala> val y = "yyy"
y: java.lang.String = yyy

scala> z.eq(y)
res6: Boolean = false

scala> z == y
res7: Boolean = false

// isInstanceOf は常に false
scala> z.isInstanceOf[String]
res5: Boolean = false

6.4 指定子(Designaters)

SimpleExpr   ::=   Path
               | SimpleExpr '.' id
  • 名前付き項(named term)、単純名 or 選択(selection)

2章のルールに従って値を参照する。たとえば x を取り囲むクラス・オブジェクトである C 中の定義/宣言により「x」が束縛されるなら単純名「x」は選択「C.this.x」と等価とみなされる。

選択「r.x」の「r」が T の安定識別子(最後が識別子で終わるパス)の場合は T において名前「x」と同一視される「r」のメンバーを参照する。

その他については式 e の「e.x」が「{ val y = e; y.x }」であるように型付けされる。

例外的に、参照するエンティティの型 T ではなく、安定型を要求するケースがある。安定型について、3.2.1 シングルトン型(Singleton Types)より引用。

安定型(stable type)とは、シングルトン型あるいは、トレイト scala.Singleton のサブ型 (subtype) であると宣言された型、のいずれかです。

パス p の型がシングルトン型 p.type の場合に以下のいずれかに該当すると安定型が要求される。

  • パス p は選択の前置子として出現し、定数を指定しない
  • 要請される型 pt が安定型である
  • 要請される型 pt が下限境界に安定型を持つ抽象型で p によって参照されるエンティティの型 T が pt と適合しない
  • パス p はモジュールを指定する(package object)

6.5 this と super(This and Super)

「this」はテンプレート or 複合型の文部分中に現れ、参照を囲む最も内側のテンプレート or 複合型によって定義されるオブジェクトを表す。

「super」は参照を囲む最も内側のクラス・オブジェクト定義の最小固有のスーパー型中のメソッド or 型を静的に参照する。

「C.super[T].x」のように super 前置子のあとにトレイト限定修飾子が続く場合「静的 super 参照(static super reference)」と呼ばれる。この場合、単純名 T 中のメソッド or 型の参照となる。

6.6 関数適用(Function Applications)

SimpleExpr    ::=  SimpleExpr1 ArgumentExprs
ArgumentExprs ::=  '(' [Exprs] ')'
                |  '(' [Exprs ','] PostfixExpr ':' '_' '*' ')'
                |  [nl] BlockExpr
Exprs         ::=  Expr {',' Expr}

関数の適用は「f(e1,...,em)」のように表し、その要素はそれぞれ以下の通り。

f: 関数
e1,...,em: 引数式

f がメソッド型「(p1:T1,...,pn:Tn)U」であれば、各引数式 ei はそれぞれメソッド型 f の引数型 Ti を要請型として型付けされる。また、もし f が多相的(polymorphic)メソッドであれば、ローカルな型推論(6.26.4)がはたらく。

f が何らかの値型を持っている場合は「f#apply(e1,...,em)」の適用とみなされる。

もし f がメソッド型 (p1 : T1,...,pn : Tn)U を持ち、引数式 ei が xi = e´i の形 (xi はパラメータ名 p1,...,pn の 1 つ)なら、引数式 ei を「名前付き引数」という。

// 名前付き引数はメソッドのみで関数オブジェクトでは使えない
scala> val f = (x: String) => x
scala> val f = (zz: String) => zz
f: String => String = <function1>

scala> f(zz = "aaa")
<console>:9: error: not found: value zz
              f(zz = "aaa")
                ^

// http://www.scala-lang.org/api/current/index.html#scala.Function1
scala> f(v1 = "aaa")
res15: String = aaa

scala> f("aaa")
res9: String = aaa

scala> def m(x: String) = x
m: (x: String)String

scala> m(x = "aaa")
res10: String = aaa

パラメータなしのメソッド型「 => T」を持つ形式上のパラメータの場合は特別でパラメータの評価規則は名前呼び出し(call-by-name)となる。標準的なパラメータの評価規則は値呼び出し。

「e :_*」はシーケンス引数。引数 (e1,...,en , e´ : _*) に適用されるなら、適用中の m の型は (p1 : T1,...,pn: Tn , ps :scala.Seq[S]) と解釈される。

Example 6.6.1

scala> def sum(xs: Int*) = (0 /: xs) ((x, y) => x + y)
sum: (xs: Int*)Int

scala> sum(1, 2, 3, 4)
res11: Int = 10

scala> sum(List(1, 2, 3, 4): _*)
res12: Int = 10

scala> sum(List(1, 2, 3, 4))
<console>:11: error: type mismatch;
 found   : List[Int]
 required: Int
              sum(List(1, 2, 3, 4))
                      ^
6.6.1 名前付き引数とデフォルト引数(Named and Default Arguments)

関数適用が名前付き引数、デフォルト引数を使う場合、以下の条件を満たす必要がある。
位置的引数というのは、

  • 名前付き引数の後に位置的引数(引数の中の何番目かでどの引数か決まるもの)が続かないこと
scala> def foo(a:Int,b:Int,c:Int) = "" + a + "," + b + "," + c
foo: (a: Int,b: Int,c: Int)java.lang.String

scala> foo(3,2,c=5)
res1: java.lang.String = 3,2,5

scala> foo(3,c=5,2)
<console>:7: error: positional after named argument.
       foo(3,c=5,2)
                 ^
  • 名前付き引数の名前は全て異なり、位置的引数によって既に指定されたものを指定しないこと
scala> foo(a=1,a=2,b=3,c=4)
<console>:9: error: parameter specified twice: a
              foo(a=1,a=2,b=3,c=4)
                       ^

scala> foo(a=1,2,b=3)
<console>:9: error: parameter specified twice: b
              foo(a=1,2,b=3)
                         ^
  • 位置的引数、名前付き引数のいずれにも指定されていない引数はデフォルト引数をもつこと
scala> foo(a=1)
<console>:9: error: not enough arguments for method foo: (a: Int, b: Int, xs: Int*)Unit.
Unspecified value parameters b, xs.
              foo(a=1)
                 ^

6.7 メソッド値(Method Values)

SimpleExpr        ::=   SimpleExpr1 '_'

パラメータを持つメソッド e の場合「e _」はイータ展開(6.26.5・メソッド型の式を等価な関数型の式に変換すること)によって関数型に変換された e を表す。

パラメータなしのメソッドまたは「=> T」の名前呼び出しパラメータの場合「e _」は型「()=>T」の関数を表し、空のパラメータリスト「()」に適用されたとき e を評価する。

「e」と「_」の間には空白を入れないと「_」が名前の一部とみなされるので注意。

6.8 型適用(Type Applications)

SimpleExpr        ::=    SimpleExpr TypeArgs

型適用 e[T1,...,Tn] は引数型 T1,...,Tn をもつ型 [a1 >: L1 <: U1,...,an >: Ln

6.9 タプル(Tuples)

SimpleExpr    ::=       '(' [Exprs] ')'

タプル式は scala.Tuplen(e1,...,en) のエイリアス

n >= 2 かつ n <= 22 となる。

scala> val unit = ()
unit: Unit = ()

scala> val single = (1)
single: Int = 1

scala> val t = (1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2)
t: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) = (1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2)

scala> val t = (1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3)
<console>:7: error: object Tuple23 is not a member of package scala
       val t = (1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3)
               ^

空のタプルは Unit 型のシングルトンな値。

6.10 インスタンス生成式(Instance Creation Expressions)

SimpleExpr        ::=     'new' (ClassTemplate | TemplateBody)

単純なインスタンス生成式は「new c」の形で「c」はコンストラクタ呼び出し。

val x: S = new c

一般的には、あるクラステンプレート t (sc with mt1 with ... with mtn {stats})のインスタンス生成式 new t となり、これは

{ class a extends t; new a }

というブロックと等価、 a はユーザプログラムからアクセスできない無名クラス。

具体的な例で言い換えると

class Something
trait Logging

の場合に t が「Something with Logging」となり、インスタンス生成式は「new Something with Logging」となり、これは「{ class annoy extends Something with Logging; new annoy }」と等価。

scala> { class anon extends Something with Logging; new anon }
res0: Something with Logging with ScalaObject = anon$1@7aca2076

構造型(structual type)の値を生成するための略記表現もある。

「{D}」がクラス本体であれば「new {D}」は「new AnyRef {D}」と等価。

scala> val closable = new { def close() = println("closed") }
closable: java.lang.Object{def close(): Unit} = $anon$1@155ecf

scala> val closable = new AnyRef { def close() = println("closed") }
closable: java.lang.Object{def close(): Unit} = $anon$1@1e607cb

6.11 ブロック(Blocks)

BlockExpr   ::=   '{' Block '}'
Block       ::=   {BlockStat semi} [ResultExpr]
  • ブロック式「{s1;...;sn;e}」はブロック文「s1,...,sn」の並びと最後の式 e から構成される。
scala> val result: String = { "xxx"; "yyy"; "zzz" }
result: String = zzz
  • 文並びで同じ名前空間中で同じ名前を束縛する二つの宣言・定義を含んではならない
scala> {val a = "a"; val a = "aaa"}
<console>:8: error: a is already defined as value a
              {val a = "a"; val a = "aaa"}
                               ^
  • ブロック式「{s1;...;sn;e}」の型は「T forSome {Q}」で T は式 e の型
scala> val result: String forSome {} = { "xxx"; "yyy"; "zzz" }
result: String forSome {  } = zzz

Q は「s1,...,sn」の1つでローカルに定義されたすべての値あるいは型名の存在節(3.2.10)を含む。

存在型の forSome を使った型定義をおさらい。

下限境界(>:)に JapaneseName を指定して JapaneseName 型かその親の型のみ受け付ける。

scala> class Name(val first: String, val last: String)
defined class Name

scala> class JapaneseName(val sei: String, val mei: String) extends Name(mei, sei)
defined class JapaneseName

scala> val names: List[T forSome { type T <: JapaneseName }] = List(new Name("Martin", "Odersky"))
<console>:9: error: type mismatch;
 found   : Name
 required: T forSome { type T <: JapaneseName }
       val names: List[T forSome { type T <: JapaneseName }] = List(new Name("Martin", "Odersky"))
                                                                    ^

scala> val names: List[T forSome { type T >: JapaneseName }] = List(new Name("Martin", "Odersky"))
names: List[T forSome { type T >: JapaneseName }] = List(Name@468a169f)

これを存在節が値 or 型名の存在(occurence)を束縛すると呼ぶ。

  • ローカル定義の type t = T は、存在節 type >: T <: T によって束縛される、 t が型パラメータをもつとコンパイルエラー
scala> class T
defined class T

scala> type t = T
defined type alias t

scala> val obj = new t[Int]
<console>:9: error: t does not take type parameters
       val obj = new t[Int]
                     ^
  • ローカル定義の val x: T = e は、存在節 val x: T によって束縛される
scala> val x: T forSome {}  = new T
x: T forSome {  } = T@7a9d6b8e
  • ローカル定義の class c extends t は、存在節 type c <: T によって束縛される、T は型 c の固有のスーパー型で最小のクラス型 or 細別型、t が型パラメータをもつとコンパイルエラー
scala> class c extends t
defined class c

scala> val x: c forSome { type c <: T } = new c
x: c forSome { type c <: T } = c@6ecae1b3

scala> class c extends t[Int]
<console>:9: error: t does not take type parameters
       class c extends t[Int]
                       ^
  • ローカル定義の object x exnteds t は、存在節 val x: T によって束縛される、T は x.type の固有のスーパー型で最小のクラス型 or 細別型
scala> object x extends t
defined module x

scala> val xx: x.type forSome { type T } = x
xx: x.type forSome { type T } = x$@70b139c9

6.12 前置、中置、後置演算(Prefix、Infix and Postfix Operations)

PostfixExpr    ::=    InfixExpr [id [nl]]
InfixExpr      ::=    PrefixExpr
                 | InfixExpr id [nl] InfixExpr
PrefixExpr     ::=    ['-' | '+' | '!' | '~'] SimpleExpr

目次。

  • 6.12.1 前置演算(Prefix Operations)
  • 6.12.2 中置演算(Infix Operations)
  • 6.12.3 後置演算(Postfix Operations)
  • 6.12.4 代入演算子(Assignment Operators)
6.12.1 前置演算(Prefix Operations)

「op e」の形で前置子オペレータ op と式 e からなる。「e.unary_op」と解釈される。

op は「+」「-」「!」「~」のいずれかの識別子。何でもいいわけではない。

scala> -1
res3: Int = -1

scala> 1.unary_-
res4: Int = -1

scala> ! true
res6: Boolean = false

scala> true.unary_!
res7: Boolean = false
6.12.2 後置演算(Postfix Operations)

後置演算子 op は任意の識別子。「e op」の形で「e.op」と解釈される。要は普通のメソッド呼び出し。ピリオド省略。

scala> 123 toString
res11: java.lang.String = 123

scala> 123.toString
res12: java.lang.String = 123
6.12.3 中置演算(Infix Operations)

中置演算子 op は任意の識別子。

優先順位は op の最初の文字で決まる。低い方から順に・・

(すべての文字(letters))
|
^
&
< >
= !
:
* / %
(他のすべての特殊文字(special characters))

結合性は、演算子 op の最後の文字によって決まる。

  • 通常は左結合だが「:」で終わるオペレータは右結合となる
case class MyString(str: String) {
  def ^:(b: MyString) = b.str
}
MyString("a") ^: MyString("b") // -> "a"

cons を実現するために存在している。

1 :: 2 :: Nil
  • もし一つの式の中に複数の中置演算子があったら優先順位の高い演算子が低い演算子より強く結びつく
scala> :paste
// Entering paste mode (ctrl-D to finish)

case class MyInt(i: Int) {
  def !(v: MyInt): Boolean = this != v
  def &(v: MyInt): MyInt = this * v
  def +(v: MyInt): MyInt = new MyInt(this.i + v.i)
  def *(v: MyInt): MyInt = new MyInt(this.i * v.i)
}

// Exiting paste mode, now interpreting.

defined class MyInt

scala> MyInt(10) ! MyInt(3) & MyInt(4)
<console>:10: error: type mismatch;
 found   : MyInt
 required: Boolean
              MyInt(10) ! MyInt(3) & MyInt(4)
                                          ^

scala> MyInt(10) ! (MyInt(3) & MyInt(4))
res27: Boolean = true
  • もし同じ優先順位の演算子「op1,...,opn」をもつ中置演算「e0 op1 e1 op2 .. op en」があれば、すべての演算子は同じ結合度である必要がある

左結合なら「((e0 op1 e1) op2 …)」になり、右結合なら「e0 op1(e1 op2(e2…))」となる。

「e1 op1 e2 op2」は「(e1 op1 e2) op2」に等価である。

6.12.4 代入演算子(Assignment Operators)

代入演算子は末尾が等号記号(「=」)になる。

以下の条件をどちらも満たさないもの。

  • 演算子の始まりも等号記号である
  • 演算子が「<=」「>=」「!=」のいずれか
var l:Int = 0
val r: Int = 1
l += r

が、以下に再解釈される。

var l: Int = 0
val r: Int = 1
l = l + r

再解釈には以下の二つの条件をどちらも満たす必要がある。

  • 左辺 l が「+=」というメンバーを持たず、また、暗黙の変換によって「+=」というメンバーが有効になっていない
  • 代入「l = l + r」が型的に正しい

6.13 型付けされた式(Typed Expressions)

6.14 アノテーション(注釈)付きの式(Annotated Expressions)

6.15 代入(Assignments)

6.16 条件式(Conditional Expressions)

6.17 while ループ式(While Loop Expressions)

6.18 do ループ式(Do Loop Expressions)

6.19 fo 内包表記と for ループ(For Comprehensions and For Loops)

6.20 return 式(Return Expressions)

6.21 throw 式(Throw Expressions)

6.22 try 式(Try Expressions)

6.23 無名関数(Anonymous Functions)

6.24 定数式(Constant Expressions)

6.25 文(Statements)

6.26 暗黙の型変換(Implicit Conversions)

6.26.1 値変換(Value Conversions)
6.26.2 メソッド変換(Method Conversions)
6.26.3 オーバーロード解決(Overloading Resolution)
6.26.4 ローカルな型推論(Local Type Inference)
6.26.5 イータ展開(Eta Expansion)