マップ

マップ(Map)はいわゆる連想配列とか辞書とか呼ばれるデータ構造だ。

マップにはイミュータブルなマップと、ミュータブルなマップがある。なんで両方あるのかはわからない。

イミュータブルなマップ

Scala で単に Map と書くとイミュータブルなマップ(scala.collection.immutable.Map)になる。

scala> val m = Map("A" -> 1, "B" -> 2, "C" -> 3)
m: scala.collection.immutable.Map[String,Int] = Map(A -> 1, B -> 2, C -> 3)

イミュータブルだから変更はできない。次のようにすると変更できるように見えるけど

scala> m.updated("B", 4)
res0: scala.collection.immutable.Map[String,Int] = Map(A -> 1, B -> 4, C -> 3)

これは新しい値が返ってきているだけで、元の m は変更されていない。

scala> m
res1: scala.collection.immutable.Map[String,Int] = Map(A -> 1, B -> 2, C -> 3)

ミュータブルなマップ

ミュータブルなマップ(scala.collection.mutable.Map)を使うには、こうする。

scala> import scala.collection.mutable
import scala.collection.mutable

scala> val m2 = mutable.Map("A" -> 1, "B" -> 2, "C" -> 3)
m2: scala.collection.mutable.Map[String,Int] = Map(A -> 1, C -> 3, B -> 2)

キー B の値を変更してみよう。

scala> m2("B") = 5

すると、変数 m2 の値自体が変更されている。

scala> m2
res3: scala.collection.mutable.Map[String,Int] = Map(A -> 1, C -> 3, B -> 5)

リストのメソッド(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

ふーん、なるほどね。