リストのメソッド(3)flatMap

flatMap は、二重のリストの内側のリストを map して、結果をフラットなリストにするメソッドだ。

Ruby で書くとこう。

irb(main):001:0> [[1,2,3], [4,5]].map{|x| x.map{|y| y * y } }.flatten
=> [1, 4, 9, 16, 25]

この、外側の map と flatten をいっぺんにできる。

scala> List(List(1,2,3), List(4,5)).flatMap(x => x.map(y => y * y))
res0: List[Int] = List(1, 4, 9, 16, 25)

さらに、ちょっと工夫すると面白い使い方ができる。

scala> List(1,2,3).flatMap(x => List(10,20).map(y => x + y))
res1: List[Int] = List(11, 21, 12, 22, 13, 23)

外側のリスト List(1,2,3) と内側に現れるリスト List(10,20) とで、あたかも二十ループのようになっている。実際これはつぎの for-comprehension と同じだ。

scala> for (x <- List(1,2,3); y <- List(10,20)) yield x + y
res1: List[Int] = List(11, 21, 12, 22, 13, 23)

というよりも、for-comprehension というのは上の flatMap のシンタックスシュガーなのだそうだ。

じゃぁ、三重のループもできるんだろうか。

scala> List(1,2,3).flatMap(x => List(10,20).flatMap(y => List(100,200).map(z => x + y + z)))
res3: List[Int] = List(111, 211, 121, 221, 112, 212, 122, 222, 113, 213, 123, 223)

できた。

でも for で書いたほうがわかりやすいかな。

リストのメソッド(2)

今日はリストの高階関数を中心に見ていこう。

foldLeftとfoldRight

まずは畳み込み関数。foldLeft は左から、foldRight は右から畳み込む。

scala> List(1,2,3,4,5).foldLeft("0")((x, y) => List(x, y).mkString("(", ",", ")"))
res0: String = (((((0,1),2),3),4),5)
scala> List(1,2,3,4,5).foldRight("0")((x, y) => List(x, y).mkString("(", ",", ")"))
res1: String = (1,(2,(3,(4,(5,0)))))

畳み込みの初期値と関数が別の引数リストになってる。何のためだろ。

map

map は写像。

scala> List(1,2,3,4,5).map(x => x * x)
res2: List[Int] = List(1, 4, 9, 16, 25)

fileter

条件に合う要素だけを抜き出す。

scala> List(1,2,3,4,5).filter(x => x % 2 == 1)
res3: List[Int] = List(1, 3, 5)

find

条件に合う最初の要素を返す。

scala> List(1,2,3,4,5).find(x => x % 2 == 0)
res4: Option[Int] = Some(2)

Option[Int] って型と Some(2) って値が返ってきた。OCaml でいう Option 型かな。Haskell だと Maybe。

takeWhile

リストの先頭から条件に合っている間だけ抽出する。

scala> List(1,2,3,4,5).takeWhile(x => x < 3)
res5: List[Int] = List(1, 2)

count

条件に合う要素を数える。

scala> List(1,2,3,4,5).count(x => x % 2 == 0)
res6: Int = 2

リストのメソッド

今日からリスト(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 が返り値の型。こっちのほうが多用されるので覚えておくこと。