リストのメソッド

今日からリスト(List)のメソッドを見ていく。

Nil

Nil は空のリストを表す。Lisp みたいだな。

scala> Nil
res0: scala.collection.immutable.Nil.type = List()

::(コンス)

リストの先頭に要素を付け足す演算子。

scala> 1 :: Nil
res1: List[Int] = List(1)

Scala の演算子っていうのは実はメソッドのシンタックスシュガーで、引数が1つのメソッドは中置記法で書ける。さらに : で終わる演算子は右側のメソッドとして解釈される。だから上の例は次のようにもかける。

scala> Nil.::(1)
res2: List[Int] = List(1)

まぁ、ふつうはこんな書きかたしないけどね。

scala> 1 :: List(2, 3, 4)
res3: List[Int] = List(1, 2, 3, 4)

scala> 1 :: 2 :: 3 :: 4 :: Nil
res4: List[Int] = List(1, 2, 3, 4)

連結

リスト同士を連結するには ++。Haskell といっしょだな。

scala> List(1, 2, 3) ++ List(4, 5)
res5: List[Int] = List(1, 2, 3, 4, 5)

mkString:文字列にフォーマッティングする

リストを文字列にする。mkString にはいくつかのバージョンがあって、まずは引数なしバージョン。これは単に要素を連結した文字列を返す。要素の型は文字列じゃなくてもいいみたいだ。

scala> List(1, 2, 3).mkString
res6: String = 123

次に引数1つのバージョン。引数をセパレータとして連結する。

scala> List(1, 2, 3).mkString("-")
res7: String = 1-2-3

最後に引数3つのバージョン。セパレータに加えて前後を囲む文字を指定する。

scala> List(1, 2, 3).mkString("<", "-", ">")
res8: String = <1-2-3>

さて、今回はこのくらいかな。次はリストの高階関数(メソッドだけど)を見ていこう。

[追記]

Nil のところで、REPL に現れる型が scala.collection.immutable.Nil.type になっている。

scala> Nil
res0: scala.collection.immutable.Nil.type = List()

Nil だけでは要素の型がわからないからこうなるみたいだ。要素の型を指定するにはこうする。

scala> Nil: List[String]
res1: List[String] = List()

覚えておこう。

Range

Range は範囲を表すオブジェクトだ。to または until 演算子を使って作る。

scala> 1 to 5
res0: scala.collection.immutable.Range.Inclusive = Range 1 to 5

scala> 1 until 5
res1: scala.collection.immutable.Range = Range 1 until 5

toList メソッドを使ってリストにしたほうがわかりやすい。

scala> (1 to 5).toList
res2: List[Int] = List(1, 2, 3, 4, 5)

scala> (1 until 5).toList
res3: List[Int] = List(1, 2, 3, 4)

上に見えるように、to は右の被演算子を範囲に含み、until は含まない。

新しいサイトを作った

今日の話題は Scala からはなれて、新しいサイトを作った話。↓ここ。

 cf. Optic Acid

JETBOY っていうレンタルサーバを借りて、WordPress で作った。ドメインも取った。

このブログはプログラミングとかサーバ管理とかの記事なので、Optic Acid のほうではもっと気ままに記事を書くつもり。いつまで続くかわからないけどね。

さて、ちょっと技術的な話もしておこう。

JETBOY っていうレンタルサーバの特徴は、ひとつには月額290円(年払い、一番安いプラン)っていう安さもあるんだけど、オール SSD と、LiteSpeed っていう Web サーバを採用しているところだろう。LiteSpeed は Apache や Nginx より3倍速いとか書いてある。

これを見て使ってみたくなったってのも、新しいサイトを作った理由のひとつだね。まぁ、そんなに速いサーバいらないって話もあるんだけどさ。

配列とリスト

Scala には配列(Array)とリスト(List)がある。

Array は ミュータブル、つまり変更ができるけど、List はイミュータブル、つまり変更ができない。これは値としてそうなのであって、変数の var と val の違いではないので注意。

Array

Array から見ていこう。次のように作る。

scala> val arr = Array(1, 2, 3, 4, 5)
arr: Array[Int] = Array(1, 2, 3, 4, 5)

Array は型変数を持つ。要素の型だ。上の例では書かなかったけど、型推論が働いて要素の型が Int になっている。ちゃんと書くならこうなる。

scala> Array[Int](1, 2, 3, 4, 5)
res0: Array[Int] = Array(1, 2, 3, 4, 5)

Array の要素の参照には ( ) を使う。多くの言語では [ ] を使っているのとちょっと違う。インデックスは 0 から。

scala> arr(3)
res1: Int = 4

Array はミュータブルなので、要素を書き換えることができる。

scala> arr
res2: Array[Int] = Array(1, 2, 3, 4, 5)

scala> arr(0) = 7

scala> arr
res4: Array[Int] = Array(7, 2, 3, 4, 5)

0 番目の要素が 7 に書き換わっているのがわかる。

と、いうのが Array なわけだけど、Scala では Array よりも List のほうを多用するらしい。

List

List は Array と似ているけど、イミュータブルなので要素の変更ができない。

scala> val lst = List(1, 2, 3, 4, 5)
lst: List[Int] = List(1, 2, 3, 4, 5)

scala> lst(0)
res5: Int = 1

scala> lst(0) = 7
<console>:13: error: value update is not a member of List[Int]
       lst(0) = 7
       ^

作り方も、要素の参照の仕方も Array と同様だけど、要素を書き換えようとするとエラーが発生する。

List は変更はできないけど、メソッドを使って新しい List を作ることができる。このへんは関数型プログラミングらしく、次々に新しい List を作りながらプログラムを組み立てるようだ。いろいろメソッドがあるみたいだけど、今日のところはこのへんで。

高階関数

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

次の例は、引数の整数に関数を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.

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