高階関数

関数を引数にとる、高階関数を定義することもできる。

次の例は、引数の整数に関数を2度適用する関数、じゃなくてメソッドだ。Scala では関数とメソッドは別のものだけど、この高階関数という用語はどちらにも使うみたい。

scala> def double(n: Int, f: Int => Int): Int = {
     |     f(f(n))
     | }
double: (n: Int, f: Int => Int)Int

見ればわかるように、n に f を2度適用している。関数引数の型の書き方に注目。で、こんなふうに使える。

scala> double(1, m => m * 2)
res0: Int = 4

scala> double(2, m => m * m)
res1: Int = 16

メソッドじゃなくて関数ではどうか。

scala> val doubleFunc = (n: Int, f: Int => Int) => f(f(n))
doubleFunc: (Int, Int => Int) => Int = $$Lambda$3227/682293791@36e7bd4d

scala> doubleFunc(1, m => m * 2)
res2: Int = 4

scala> doubleFunc(2, m => m * m)
res3: Int = 16

ちゃんとできるね。

関数のカリー化

Scala は関数型プログラミング言語でもあるので、関数のカリー化もできる。

scala> val addCurried = (x: Int) => ((y: Int) => x + y)
addCurried: Int => (Int => Int) = $$Lambda$3206/517834570@1a67f250

scala> val add1 = addCurried(1)
add1: Int => Int = $$Lambda$3241/1505134204@7c676bd

scala> add1(3)
res0: Int = 4

いや、これ、たんに関数から関数を返してるだけじゃないか。もっとカリー化専用の構文があるのかと思った。

ところで、メソッドのほうは引数の代わりに _ (アンダースコア)を使うことで関数を得ることができる。

scala> def add(x: Int, y: Int): Int = x + y
add: (x: Int, y: Int)Int

scala> val addFun = add _
addFun: (Int, Int) => Int = $$Lambda$3266/46821433@2a1ea898

scala> addFun(3, 5)
res2: Int = 8

そして、引数リストを複数に分けたメソッドを関数化すると、カリー化される。

scala> def addMultiParameter(x: Int)(y: Int): Int = x + y
addMultiParameter: (x: Int)(y: Int)Int

scala> val addM = addMultiParameter _
addM: Int => (Int => Int) = $$Lambda$3276/2016773972@70b0e2a6

scala> val add2 = addM(2)
add2: Int => Int = $$Lambda$3277/527492379@5b248e9d

scala> add2(3)
res3: Int = 5

ふーん、なるほどね。

関数

型パラメータの変位指定とか境界とかを読んでいたんだけど、いまいちよくわからないので、とりあえずパスして関数に行く。

関数

Scala の関数は、ほかの言語とはちょっと趣が違う。関数は単に Function0 から Function22 までのトレイトのサブクラスのインスタンスなのだそうだ。0 とか 22 とかの数字は引数の数らしい。例えば、2つの整数を足し算する関数 add は次のように定義する。

scala> val add = new Function2[Int, Int, Int] {
     |     def apply(x: Int, y: Int): Int = x + y
     | }
add: (Int, Int) => Int = <function2>

ああ、ここでもよくわからないぞ。トレイトは直接インスタンス化できないんじゃなかったのか?それと new キーワードでインスタンスを作るときにメソッドの定義もできるの?いつそんな話があった?

まぁいい。そういうものだと思っておこう。Function2 というのは引数を2つとる関数(の元)だけど、型パラメータが3つあるのは最後の1つは返り値の型のようだ。こういうふうに使う。

scala> add.apply(200, 300)
res0: Int = 500

または、apply メソッドは特別扱いされるので次のようにも書ける。こっちのほうが関数らしい。

scala> add(200, 300)
res1: Int = 500

無名関数

注意する点として、厳密には add というのは変数の名前であって、そこに代入されている関数オブジェクトの名前ではない、ということがある。つまりこれは無名関数なわけだ。そして無名関数らしくかけるように、シンタックスシュガーが用意されている。

scala> val add2 = (x: Int, y: Int) => x + y
add2: (Int, Int) => Int = $$Lambda$3280/2126394127@24301165

うーん、冒頭の new Function2 を使って定義したときと、REPL のレスポンスが違うな。そういうものなんだろうか。何か違うのか?

まぁいいや。とりあえずこれも無名関数が変数 add2 に代入されているってことのようだ。で、次のように使える。

scala> add2(1, 3)
res2: Int = 4

関数の型

上のように定義した関数の型は、本来 FunctionN[…] のように表記すべきところ、これまたシンタックスシュガーが用意されている。次のようになる。

(N1, N2, ..., NN) => B

N1 から NN までが引数の型で、B が返り値の型。こっちのほうが多用されるので覚えておくこと。

タプル

昨日は型パラメータを2つとる Pair というクラスを作った。

scala> class Pair[A, B](val a: A, val b: B)
defined class Pair

こういうコンテナ型は、メソッドの返り値に複数の値を返したいようなときに使える(Scala のメソッドは Go と違って1つの値しか返せない)。例えば、割り算の商とあまりを返す divide メソッドはこんなふうだ。

scala> def divide(m: Int, n: Int): Pair[Int, Int] = new Pair(m / n, m % n)
divide: (m: Int, n: Int)Pair[Int,Int]

scala> val x = divide(7, 3)
x: Pair[Int,Int] = Pair@12fd3b16

scala> x.a
res0: Int = 2

scala> x.b
res1: Int = 1

こういうコンテナはよく使うので、Scala には Tuple1 から Tuple22 までのクラス(数字は要素の数)が用意されている。しかも:

scala> new Tuple2(3, 1)
res2: (Int, Int) = (3,1)

と書かずに

scala> (3, 1)
res3: (Int, Int) = (3,1)

と書くことができる。この書き方によれば、上のメソッドは次のように簡潔に書ける。

scala> def divide2(m: Int, n: Int): (Int, Int) = (m / n, m % n)
divide2: (m: Int, n: Int)(Int, Int)

scala> divide2(7, 3)
res4: (Int, Int) = (2,1)

ところで、Tuple1 ってのは要素数1のタプルだろう。これって存在意義あんの?簡略な記法じゃ作れないし。

scala> (1)
res5: Int = 1

scala> (1,)
<console>:1: error: illegal start of simple expression
       (1,)
          ^
scala> new Tuple1(1)
res6: (Int,) = (1,)

型パラメータ(type parameter)

Scala では、クラスを定義するときには決められないような型を持つことができる。この型は仮の名前をつけておき、実際にクラスのインスタンスを作るときに決まるようにする。これを型パラメータと呼ぶ。型パラメータはたとえば、中にどんな型が入るかわからないコンテナのようなクラスを作るのに役立つ。

型パラメータを使うには、クラス定義の時にクラス名の後に [ ] をつけてその中にパラメータ(習慣的に A,B,C…とつける)を書く。次の例は、データを出し入れできる Cell クラスだ。

scala> class Cell[A](var value: A) {
     |     def put(newValue: A): Unit = {
     |         value = newValue
     |     }
     |
     |     def get(): A = value
     | }
defined class Cell

この定義の中で、A が型パラメータ。この段階ではどんな型が入るのかはわからない。次のように使う。

scala> val cell = new Cell(2)
cell: Cell[Int] = Cell@716427b1

scala> cell.get()
res0: Int = 2

scala> cell.put(3)

scala> cell.get()
res2: Int = 3

定義時にはどんな型が入るかわからないけど、最初にインスタンスを作ったときに型が決まる(この例では Int)ので、違う型は put できない。

scala> cell.put("Hello")
<console>:13: error: type mismatch;
 found   : String("Hello")
 required: Int
       cell.put("Hello")
                ^

もちろん、インスタンス作成時には文字列でも何でも使える。

scala> val cell2 = new Cell("Andy")
cell2: Cell[String] = Cell@1ae85afc

scala> cell2.get()
res4: String = Andy

scala> cell2.put("Bill")

scala> cell2.get()
res6: String = Bill

型パラメータは複数とることができる。つぎの Pair クラスは2つの型パラメータをとっている。

scala> class Pair[A, B](val a: A, val b: B)
defined class Pair

A と B は別々の型でもいいし、同じ型でもいい。こんなふうに使う。

scala> val p1 = new Pair(1, "Andy")
p1: Pair[Int,String] = Pair@65c4462a

scala> p1.a
res7: Int = 1

scala> p1.b
res8: String = Andy

scala> val p2 = new Pair(2, 3)
p2: Pair[Int,Int] = Pair@404307dd

scala> p2.a
res9: Int = 2

scala> p2.b
res10: Int = 3

トレイトの継承の線形化

昨日と同じように、菱形継承問題を考える。

scala> trait TraitA {
     |     def greet(): Unit
     | }
defined trait TraitA

scala> trait TraitB extends TraitA {
     |     override def greet(): Unit = println("Good morning!")
     | }
defined trait TraitB

scala> trait TraitC extends TraitA {
     |     override def greet(): Unit = println("Good evening!")
     | }
defined trait TraitC

昨日と違うのは、TraitB と TraitC で greet メソッドを実装するときに override キーワードをつけているところだ。この場合、つぎのように、単純に2つを継承したクラスを作ってもエラーにならない。

scala> class ClassA extends TraitB with TraitC
defined class ClassA

ここで、ClassA の greet メソッドを呼び出すと何を出力するか。試してみよう。

scala> (new ClassA).greet()
Good evening!

Good evening! と表示された。ということは TraitC の greet メソッドが呼ばれたってことだ。

つぎに、TraitB と TraitC の継承順を入れ替えた ClassB を考えよう。

scala> class ClassB extends TraitC with TraitB
defined class ClassB

scala> (new ClassB).greet()
Good morning!

今度は TraitB の greet メソッドが呼び出されている。

Scala では、こういう形で複数のトレイトを継承(ミックスイン)した場合、あとからミックスインしたトレイトが優先される。この機能をトレイトの線形化と呼ぶ。

さて、ここからがちょっとよくわからない。メソッドの中で super を使うことで親トレイトのメソッドを呼び出すことができる。定義はこんなふうだ。

scala> trait TraitA {
     |     def greet(): Unit = println("Hello!")
     | }
defined trait TraitA

scala> trait TraitB extends TraitA {
     |     override def greet(): Unit = {
     |         super.greet()
     |         println("My name is Terebi-chan.")
     |     }
     | }
defined trait TraitB

scala> trait TraitC extends TraitA {
     |     override def greet(): Unit = {
     |         super.greet()
     |         println("I like niconico.")
     |     }
     | }
defined trait TraitC

scala> class ClassA extends TraitB with TraitC
defined class ClassA

scala> class ClassB extends TraitC with TraitB
defined class ClassB

ClassA の greet メソッドを呼んでみよう。

scala> (new ClassA).greet()
Hello!
My name is Terebi-chan.
I like niconico.

あとからミックスインされた TraitC の greet メソッドが呼ばれるはずだから I like niconico. と出力されるのはわかる。super を使って親トレイトの greet メソッドも呼んでいるんだから Hello! と出力されるのもわかる。だけどなんで My name is Terebi-chan. と出力されるんだ?これは TraitB のメソッドの出力のはずだろう?

TraitB と TraitC の間には親子関係がないはずなのに、まるで TraitB が TraitC の親トレイトであるかのようになっている。線形化ってそういうものなのか?

ClassC では出力の順番が変わる。

scala> (new ClassB).greet()
Hello!
I like niconico.
My name is Terebi-chan.

線形化の順番が違うので出力の順番も違うってことだろう。とは思うけど、なんとなく納得がいかない。

菱形継承問題

Scala のクラスは単一継承だけど、トレイトは複数継承できるので菱形継承問題が起きる。菱形継承問題というのは次のようなものだ。

greet メソッドを定義した TraitA と、それを継承してそれぞれ別の greet メソッドを実装した TraitB と TraitC があるとする。

scala> trait TraitA {
     |     def greet(): Unit
     | }
defined trait TraitA

scala> trait TraitB extends TraitA {
     |     def greet(): Unit = println("Good morning!")
     | }
defined trait TraitB

scala> trait TraitC extends TraitA {
     |     def greet(): Unit = println("Good evening!")
     | }
defined trait TraitC

ここで TraitB と TraitC の両方を継承したクラスを考えよう。すると、継承の系統図が菱形になる。で、どこが問題かというと、greet メソッドの実装が衝突している、ということだ。これを菱形継承問題という。

Scala で単純にこういう継承を作るとエラーになる。

scala> class ClassA extends TraitB with TraitC
<console>:13: error: class ClassA inherits conflicting members:
  method greet in trait TraitB of type ()Unit  and
  method greet in trait TraitC of type ()Unit
(Note: this can be resolved by declaring an override in class ClassA.)
       class ClassA extends TraitB with TraitC
             ^

TraitB と TraitC を継承した ClassA で greet メソッドが衝突していると怒られている。

これを解決するには ClassA で greet メソッドをオーバーライドしてやればいい。

scala> class ClassA extends TraitB with TraitC {
     |     override def greet(): Unit = println("How are you?")
     | }
defined class ClassA

継承元の TraitB の greet メソッドを呼び出したいときには次のように super キーワードを使う。[ ] の中に呼び出したいほうのトレイト名を書けばいい。

scala> class ClassB extends TraitB with TraitC {
     |     override def greet(): Unit = super[TraitB].greet()
     | }
defined class ClassB

呼び出した結果はそれぞれ次のようになる。

scala> (new ClassA).greet()
How are you?
scala> (new ClassB).greet()
Good morning!

トレイト

聴きなれない単語が出てきた。トレイトというのは、Scala のオブジェクト指向プログラミングにおけるモジュール化の中心的な概念らしい。トレイトの特徴は次の3つだ。

  • 1つのクラスやトレイトに複数のトレイトを継承(ミックスイン)できる
  • 直接インスタンス化できない
  • クラスパラメータ(コンストラクタの引数)をとることができない

とりあえずは Ruby のモジュールのようなものだと理解した。

トレイトの定義

定義構文はシングルトンオブジェクトの定義に似ていて、object キーワードの代わりに trait キーワードを使う。

scala> trait Hello {
     |     val mes: String = "Hello, World!"
     |     def hello(): Unit = println(mes)
     | }
defined trait Hello

ただし、そのまま使うことはできない。

scala> Hello.hello
<console>:12: error: not found: value Hello
       Hello.hello
       ^

1つのクラスやトレイトに複数のトレイトを継承(ミックスイン)できる

トレイトの場合は継承というよりミックスインということが多いらしい。extends キーワードを使う継承は1つのクラスしかすることができないけど、with キーワードを使うミックスインは複数できる。

scala> trait TraitA
defined trait TraitA

scala> trait TraitB
defined trait TraitB

scala> class ClassA
defined class ClassA

scala> class ClassB
defined class ClassB

scala> class ClassC extends ClassA with TraitA with TraitB
defined class ClassC

ここで ClassC は ClassA を継承し、TraitA と TraitB をミックスインしている。一方、with キーワードを使っても ClassA と ClassB を継承することはできない。

scala> class ClassD extends CrassA with ClassB
<console>:12: error: not found: type CrassA
       class ClassD extends CrassA with ClassB
                            ^

直接インスタンス化できない

scala> val hello = new Hello
<console>:12: error: trait Hello is abstract; cannot be instantiated
       val hello = new Hello
                   ^

クラスパラメータ(コンストラクタ引数)をとることができない

直接インスタンス化できないんだからコンストラクタ引数をとれないのは当然だけど、じゃあ、定義時に決まらないフィールドはどうすればいいかというと、継承先のクラスで上書きしてやればいい。

scala> trait TraitA {
     |     val name: String
     |     def printName(): Unit = println(name)
     | }
defined trait TraitA

scala> class ClassA(val name: String) extends TraitA
defined class ClassA

scala> val a = new ClassA("Bill")
a: ClassA = ClassA@21bc3814

scala> a.printName
Bill

TraitA の name フィールドは実装のない抽象フィールドだけど、trait のまえに abstract キーワードをつけなくて構わない。フィールドの実装は ClassA で与えられている。ここで気が付いたけど、1つだけ継承するときはそれがトレイトでも extends キーワードを使う。

さてここまででトレイトの基本を見てきた。つぎはトレイトの機能を見ることにしよう。というところでいったんここまで。

コンパニオンオブジェクト

同じファイル内において、クラスと同じ名前で定義されたシングルトンオブジェクトをコンパニオンオブジェクトと呼ぶ。コンパニオンオブジェクトは対応するクラスに対して特権的なアクセス権を持っていて、private なフィールドに対してもアクセスできる。

試してみよう。なお、REPL で試すには :paste コマンドを使って、ペーストモードになってからクラスとコンパニオンオブジェクトを一緒に定義する。こうしないと、REPL が正しく認識できないらしい。先のエントリーで警告が出てたのはこのせいだな。

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Person(val name: String, var age: Int, private var weight: Int)

object Person {
    def printWeight(): Unit = {
        val andy = new Person("Andy", 27, 65)
        println(andy.weight)
    }
}

// Exiting paste mode, now interpreting.

defined class Person
defined object Person

ペーストモードを抜けるには Ctrl-D。

で、上のように定義したのがクラス Person とそのコンパニオンオブジェクト Person だ。コンパニオンオブジェクトの中から、プライベートは weight フィールドにアクセスしている。

じゃあ試してみよう。

scala> Person.printWeight
65

この通り、weight の値が出力された。

シングルトンオブジェクト

Scala では、クラスのほかに、object キーワードを使うことでシングルトンオブジェクトを作ることができる。シングルトンオブジェクトは、クラスとは違ってインスタンス化せずにそのまま使えるオブジェクトだ。

使い道としては次の2つがあげられる。

  • ユーティリティメソッドやグローバルな状態の置き場
  • 同名クラスのインスタンスのファクトリメソッド

まず、1つ目の使い道を見てみよう。シングルトンオブジェクトの定義構文はクラスの定義とほぼ同じで、class キーワードの代わりに object キーワードを使う。

scala> object Foo {
     |     def hello(): Unit = println("Hello, World!")
     | }
defined object Foo

この例では使っていないけど、クラスやトレイトを継承することもできる。そしてこの Foo オブジェクトはインスタンス化することなくそのまま使うことができる。

scala> Foo.hello
Hello, World!

もう1つの使い道、ファクトリメソッドについても見てみよう。同名のクラス Point をシングルトンオブジェクト Point を定義する。

scala> class Point(val x: Int, val y: Int) {
     |     override def toString(): String = "(" + x + ", " + y + ")"
     | }
defined class Point

scala> object Point {
     |     def apply(x: Int, y: Int): Point = new Point(x, y)
     | }
defined object Point
warning: previously defined class Point is not a companion to object Point.
Companions must be defined together; you may wish to use :paste mode for this.

警告が出ているけどとりあえずわきに置いておく。

シングルトンオブジェクトの apply というメソッドは Scala によって特別扱いされ、Point(x, y) という記述があったときに Point.apply(x, y) と解釈される。つまり、Point クラスのインスタンスを作るのに、new Point(2, 3) とする代わりにシングルトンオブジェクトを使って Point(2, 3) とすることができる。試してみよう。

scala> val p = Point(2, 3)
p: Point = (2, 3)

scala> p.toString
res1: String = (2, 3)

ちなみに toString というメソッドはどんなクラスにも定義されているらしい。上の Point クラスは、明示的には何も継承していないけど暗黙に何かのクラスを継承していて(たぶん)、そのクラスで toString メソッドが定義されているので、override キーワードを使って再定義している。

toString メソッドをオーバーライドすると、REPL のレスポンスに現れる値の表示も変わって (2, 3)  となる。これは REPL が値を表示するのに、暗黙に toString を呼び出しているからだね(たぶん)。