抽象メンバー、抽象クラス

クラスの定義時点では実装を持たず、サブクラスで実装されるようなメソッドやフィールドを抽象メンバーと呼ぶ。また、抽象メンバーを1つ以上持つクラスを抽象クラスと呼ぶ。

Scala では、抽象クラスは abstract キーワードをつけて定義する。そして、抽象メンバーの定義は、メソッドやフィールドの本体部分がない形とする。

以下に、抽象フィールド mes を持つ抽象クラス Hoge を見よう。

scala> abstract class Hoge {
     |     val mes: String
     |     def shout(): Unit = println(mes + "!!!!")
     | }
defined class Hoge

そしてこの mes に実装を与えるのは、Hoge を継承したサブクラス Fuga だ。

scala> class Fuga extends Hoge {
     |     val mes: String = "Fuga"
     | }
defined class Fuga

さて、じゃあ Fuga を使ってみよう。

scala> val fuga = new Fuga
fuga: Fuga = Fuga@409c6c89

scala> fuga.shout
Fuga!!!!

こんな感じだ。

そして最後に追加情報。クラス定義やインスタンスの生成時にも引数がなければカッコを省略できる。

クラスの継承

単一継承

Scala のクラスの継承は単一継承のようだ。もう少し言うと、スーパークラスのほかにトレイトというものを複数継承できるようだけど、トレイトについては日を改める。

クラスを継承するためには、extends キーワードに続けてスーパークラス名をつければいい。例を示そう。

scala> class Foo() {
     |     def foo(): Unit = println("Foo")
     | }
defined class Foo

scala> class Bar() extends Foo {
     |     def bar(): Unit = println("Bar")
     | }
defined class Bar

ここではクラス Bar がクラス Foo を継承している。Foo にはメソッド foo があり、Bar には Foo から継承したメソッド foo と Bar で定義されたメソッド bar がある。

scala> val foo = new Foo()
foo: Foo = Foo@3e81c10b

scala> foo.foo
Foo

scala> val bar = new Bar()
bar: Bar = Bar@5d842ce8

scala> bar.foo
Foo

scala> bar.bar
Bar

override

継承したクラス(サブクラス)で、スーパークラスのメソッドをオーバーライドしたいときには、明示的に override キーワードをつける必要がある。これによって、オーバライドするつもりで新しいメソッドを定義してしまったり、逆に新しいメソッドを定義するつもりでオーバーライドしてしまうようなことを防いでいるようだ。

scala> class Baz() extends Foo {
     |     override def foo(): Unit = println("Baz")
     | }
defined class Baz

scala> val baz = new Baz()
baz: Baz = Baz@7b6dd92d

scala> baz.foo
Baz

この例では、Baz でメソッド foo をオーバーライドしている。

ところで、何気なく書いたけど、引数のないメソッドの呼び出しはカッコを省略できるんだな。

複数の引数リストを持つメソッドと部分適用

Scala では複数の引数リストを持つメソッドを定義できる。読んで字の如く、引数リストが複数あるってことだ。

scala> class Adder {
     |     def add(x: Int)(y: Int) = x + y
     | }
defined class Adder

こんなふうに、引数リストを囲むカッコを連ねる。呼び出すときも同様。

scala> val adder = new Adder()
adder: Adder = Adder@2a83bab5

scala> adder.add(2)(3)
res0: Int = 5

引数リストの代わりに _ を使うと部分適用ができる。

scala> val fun = adder.add(3) _
fun: Int => Int = $$Lambda$3397/284877983@4dddc7e7

この fun は関数オブジェクトのようなものなのかな。残りと引数を与えると値が返ってくる。

scala> fun(4)
res1: Int = 7

いまは後ろの引数リストの代わりに _ を使ったけど、前の引数リストの代わりには使えないんだろうか。

scala> val fun2 = adder.add _ (3)
<console>:1: error: ';' expected but '(' found.
       val fun2 = adder.add _ (3)
                              ^

ダメか。

部分適用は、単一の引数リストでも使える。Adder クラスを定義しなおしてみよう。

scala> class Adder {
     |     def add(x: Int, y: Int) = x + y
     | }
defined class Adder

この新しい Adder クラスの add メソッドで部分適用を試してみる。

scala> val fun: Int => Int = adder.add(2, _)
fun: Int => Int = $$Lambda$3365/582762225@4e9e818e

scala> fun(3)
res0: Int = 5

関数オブジェクト(?)の型はを省略できないみたいだ。省略するとエラーになる。

scala> val fun = adder.add(2, _)
<console>:12: error: missing parameter type for expanded function ((x$1: ) => adder.add(2, x$1))
       val fun = adder.add(2, _)
                              ^

ところで、こっちの形式なら後ろの引数を部分適用できるんだろうか。

scala> val fun2: Int => Int = adder.add(_, 3)
fun2: Int => Int = $$Lambda$3381/567903408@64679683

scala> fun2(5)
res1: Int = 8

できた。

メソッド定義

クラスにはメソッドを定義できる。簡単な例からいこう。

scala> class Person(val name: String) {
| def hello(): String = {
| "Hello, I'm " + name + "!"
| }
| }
defined class Person

def で始まるのがメソッド定義だ。この例では引数をとらない hello メソッドを定義している。= を使うのがちょっと珍しい。

呼び出すには . (ドット)を使う。

scala> val andy = new Person("Andy")
andy: Person = Person@8d3da30

scala> andy.hello()
res0: String = Hello, I'm Andy!

メソッドの本体を { } で囲っているけど、これは一般には複数の式からなるから必要なのであって、メソッド定義の構文として必要なわけではない。言い換えると、メソッド本体が式一つだけなら { } はなくてもいい。こんなふうに。

scala> class Point(val x: Int, val y: Int) {
| def +(p: Point): Point = new Point(x + p.x, y + p.y)
| }
defined class Point

さらっと書いたけど、演算子がメソッドとして定義できるんだな。なんか条件とかあるんだろうか。

ま、とにかくこんなふうに使える。

scala> val p1 = new Point(1, 1)
p1: Point = Point@6b93c152

scala> val p2 = new Point(2, 2)
p2: Point = Point@cec7389

scala> val p3 = p1 + p2
p3: Point = Point@1514f0c2

scala> p3.x
res0: Int = 3

scala> p3.y
res1: Int = 3

今日は時間がないのでここまで。

クラス

クラス定義

Scala はオブジェクト指向プログラミング言語でもあるので、当然クラスの定義もできる。もっとも簡単な例は次のようになる。

scala> class Point(_x: Int, _y: Int) {
| val x = _x
| val y = _y
| }
defined class Point

これは x と y というフィールドを持つクラスだ。フィールドの宣言には val と var が使える。もちろん val で宣言したフィールドは代入できない。

クラスをインスタンス化するには new キーワードを使う。Point というクラス名の後につづく ( ) 内の引数がそのままコンストラクタの引数になる。

scala> val p = new Point(3, 2)
p: Point = Point@1409fe14

コンストラクタの引数名とフィールド名が同じでよければ、次のように簡単に書ける。

scala> class Point(val x: Int, val y: Int)
defined class Point

フィールドのアクセス制御

フィールドはデフォルトでパブリックなので、外側から参照することができる。

scala> p.x
res0: Int = 3

scala> p.y
res1: Int = 2

パブリックにしたくなければ、フィールドの宣言に private または protected キーワードをつける。private をつけるとそのクラスの中だけから、protected をつけるとクラスの派生クラスだけからアクセスできるようになる。private をつけた例を見てみよう。

scala> class Person(_name: String, _age: Int) {
| val name = _name
| private var age = _age
| }
<console>:13: warning: private var age in class Person is never used
private var age = _age
^

おっと、警告が出た。プライベートは変数 age が使われない、みたいなことを言われてるようだ。ま、そりゃそうだ。今はフィールドを操作するメソッドがないから、外からアクセスできないフィールドだけあっても役に立たないからな。でも、とりあえずクラス定義はできて、インスタンス化もできる。

scala> val andy = new Person("Andy", 27)
andy: Person = Person@4bd808d0

scala> andy.name
res0: String = Andy

scala> andy.age
<console>:13: error: variable age in class Person cannot be accessed in Person
andy.age
^

パブリックな name にはアクセスできて、プライベートな age にはアクセスできない様子がわかる。

型によるパターンマッチ

match 式では、値の型によるパターンマッチもできる。パターンには変数の後に : に続けて型を書く。こんなふうに。

scala> val obj: AnyRef = "String Literal"
obj: AnyRef = String Literal

scala> obj match {
| case v:java.lang.Integer =>
| println("Integer!")
| case v:String =>
| println(v)
| }
String Literal

AnyRef という型は、あらゆる参照型を代入できる型。よくわからないけどとりあえずそういうものだと思っておく。

で、この例では値の実体が文字列なので、初めの java.lang.Integer にはマッチせず、2番目の String にマッチしている。値は変数 v に束縛されるので後から使うことができる。

さて、ここで疑問なのは、どうして java.lang.Integer なんて型がいきなり出てきたかってこと。Int じゃダメなのか。試してみよう。

scala> obj match {
| case v:Int =>
| println("Int!")
| case v:String =>
| println(v)
| }
<console>:14: error: pattern type is incompatible with expected type;
found : Int
required: Object
case v:Int =>
^

ダメだった。なんかパターンの型の互換性がどうとか言ってる。Object を期待しているところに Int が来たと。

なんというか、まだ Scala の型システムについて理解してないんだけど、いきなり Java の型が出てくるのは気持ちが悪い。Scala の型だけで済ますことはできないんだろうか。

match式

match 式は、値のパターンマッチによって処理を分岐する構文(式)だ。

基本的な使い方

まずはいちばん基本的な、switch 文のような使い方。

scala> val name = "Taro"
name: String = Taro

scala> name match {
| case "Taro" => "male"
| case "Jiro" => "male"
| case "Hanako" => "female"
| }
res0: String = male

変数 name の値によって分岐している。キーワード match を対象となる変数の後に書くのが特徴的だな。res0 とあるように、match 式も値を返す。

match の対象には String 型じゃなく、ほかの型も使える。

scala> val n = 5
n: Int = 5

scala> n match {
| case 1 => "one"
| case 2 => "two"
| case 3 => "three"
| case _ => "many"
| }
res1: String = many

最後の case の _ は、ワイルドカードパターンといって、どんな値にもマッチする。Scala では分岐の漏れを防ぐためによく使われるようだ。

パターンをまとめる

複数のパターンで同じ処理をしたいときには、| で区切ってパターンを並べることでまとめることができる。こんなふうに。

scala> "def" match {
| case "abc" | "def" =>
| println("first")
| println("second")
| case "ghi" =>
| println("third")
| }
first
second

ここで気が付いたけど、分岐先が複数の式になってても { } で囲う必要はないんだな。

パターンマッチによる値の取り出し

パターンに一種の変数を使うことで、対応する値を取り出すことができる。

scala> val lst = List("A", "B", "C")
lst: List[String] = List(A, B, C)

scala> lst match {
| case List("A", b, c) =>
| println("b = " + b)
| println("c = " + c)
| case _ =>
| println("nothing")
| }
b = B
c = C

この例では、先頭が “A” であるような3要素のリストにマッチし、2番目と3番目の要素がそれぞれ b と c に代入されている。

ガード式

パターンにガード式をつけることで、条件を加えることができる。たとえば、「先頭が “A” のリストで、かつ、2番目が “B” でない」というパターンは次のように書ける。

scala> lst match {
| case List("A", b, c) if b != "B" =>
| println("b = " + b)
| println("c = " + c)
| case _ =>
| println("nothing")
| }
nothing

この例では、「先頭が “A” のリスト」にはマッチするけど「if b != “B”」が偽になるので最初のパターンにはマッチせず、あとのワイルドカードパターンにマッチして nothing が返ってきている。

asパターン

パターンの前に変数名と @ をつけることで、パターンにマッチした値を変数に束縛(「束縛」って用語を使ってるな)することができる。これは後からパターンの値を参照するのに便利。

scala> val lst2 = List(List("A"), List("B", "C"))
lst2: List[List[String]] = List(List(A), List(B, C))

scala> lst2 match {
| case List(a@List("A"), x) =>
| println(a)
| println(x)
| case _ =>
| println("nothing")
| }
List(A)
List(B, C)

ここでは、List(“A”) というパターンの値を変数 a に束縛して後から使っている。

というところで、とりあえずはここまで。

for式

Scala の for 式は、ほかのプログラミング言語よりも機能が豊富だ。まずは簡単な例から見てみよう。

scala> for (x <- 1 to 3) {
| println("x = " + x)
| }
x = 1
x = 2
x = 3

見ての通り x を 1 から 3 まで繰り返しているけど、このカッコの中の x <- 1 to 3 をジェネレータと呼ぶ。1 to 3 は 1 から 3 まで(3 を含む)の範囲を表す式でこれを順に x に代入するものだ。ジェネレータは複数あってもいい。

scala> for (x <- 1 to 3; y <- 1 until 3) {
| println("x = " + x + " y = " + y)
| }
x = 1 y = 1
x = 1 y = 2
x = 2 y = 1
x = 2 y = 2
x = 3 y = 1
x = 3 y = 2

今度の例では x と y のジェネレータを使っている。1 until 3 というのは 1 から 2(3を含まない)の範囲を表している。

ジェネレータには条件を付けることができる。次の例では、x と y が等しくない場合だけを抽出している。

scala> for (x <- 1 to 3; y <- 1 until 3 if x != y) {
| println("x = " + x + " y = " + y)
| }
x = 1 y = 2
x = 2 y = 1
x = 3 y = 1
x = 3 y = 2

for 式は、コレクションの要素を1つずつたどっていくこともできる。いわゆる foreach のような使い方だ。次の例ではリストの要素を順に出力している。

scala> for (e <- List("A", "B", "C", "D", "E")) {
| println(e)
| }
A
B
C
D
E

さらに、for 式ではコレクションから新しいコレクションを作ることもできる。

scala> for (e <- List("A", "B", "C", "D", "E")) yield {
| "Pre" + e
| }
res5: List[String] = List(PreA, PreB, PreC, PreD, PreE)

ここでのポイントは、ブロック式の前についている yield キーワードだ。yield をつけることで単に繰り返すのではなく、新しいコレクションを作ってくれる。yield キーワードを使ったこの形を for-comprehension と呼ぶことがある。

while式

while 式も構文は普通。注意しなきゃいけないのは、カッコの中の条件式が Boolean 型じゃないといけないことくらいだ。

scala> while (i <= 10) {
| println("i = " + i)
| i = i + 1
| }
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10

while 式も式なので値を持つ。ただし、必ず Unit 型の値 () になる。上の例ではわかりにくいので、変数に代入してみよう。

scala> var b = while (i <= 10) {
| println("i = " + i)
| i = i + 1
| }
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
b: Unit = ()

このとおり。

if式

今日から Scala の制御構文を見ていく。まずは if 式。

……の前に、ブロック式。ブロック式というのは、ドワンゴの研修資料で便宜的に使われている用語だけど、要するに、複数の式を { と } で囲ったものだ。Scala は中の式を順番に評価し、最後の式の値がブロック式の値となる。

scala> { println("A"); println("B"); 1 + 2; }
A
B
res0: Int = 3

見ての通り、A と B が順に出力され、最後の 1 + 2 を評価した結果がブロック式の値として返ってきている。Scala の式は ; で区切る代わりに改行で区切ってもいいので次のようにも書ける。

scala> {
| println("A")
| println("B")
| 1 + 2
| }
A
B
res1: Int = 3

さて、if 式だけど、いたって普通の構文だ。見たほうが早い。

scala> var age = 17
age: Int = 17

scala> if (age < 18) {
| "18歳未満です"
| } else {
| "18歳以上です"
| }
res2: String = 18歳未満です

scala> age = 18
age: Int = 18

scala> if (age < 18) {
| "18歳未満です"
| } else {
| "18歳以上です"
| }
res3: String = 18歳以上です

if に続くカッコの中の条件式は Boolean 型である必要がある。

if 式は式なので、それ自体が値を持つ。上の例でも「18歳未満です」とかの値が返ってきているのがわかる。ということは変数へ代入もできるってことだ。

scala> var a = if (age < 18) {
| "18歳未満です"
| } else {
| "18歳以上です"
| }
a: String = 18歳以上です

変数 a に代入された。

ところで、else 以降は省略が可能だ。その場合には Unit 型の値 () が補われたものとして評価される。……ってことは、条件式が真の場合と偽の場合で返ってくる方が違うことになるんだけどいいんだろうか。

scala> a = if (age < 18) {
| "18歳未満です"
| }
<console>:13: error: type mismatch;
found : Unit
required: String
a = if (age < 18) {
^

あ、やっぱりエラーになった。こういう時はどうするんだろう。