型によるパターンマッチ

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 に束縛して後から使っている。

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

MediaWikiのバックアップ

先日、データのバックアップサーバを作ったので、MediaWiki で運用している wiki のバックアップもすることにした。

MediaWiki でバックアップが必要なのはつぎの2つ。

  • /var/www/html/wiki 以下のファイル群
  • データベース

ファイル群は単純に rsync でバックアップする。データベースは mysqldump コマンドでファイルに書きだしたあと、gzip で圧縮してから rsync でバックアップする。

こんなスクリプトにした。

mysqldump -hlocalhost -uxxxx -pyyyy wiki | gzip > wiki.sql.gz
rsync -av -e "ssh -i ~/.ssh/synckey" --delete /home/takatoh/wiki.sql.gz takatoh@nightschool:/mnt/pikaia/backup/wiki/db
rsync -azv -e "ssh -i ~/.ssh/synckey" --delete /var/www/html/wiki/ takatoh@nightschool:/mnt/pikaia/backup/wiki/wiki

xxxx はデータベースのユーザー名、yyyy はパスワード、wiki がデータベース名だ。

これで試してみたら OK だったので、cron に登録して毎日バックアップすることにした。

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) {
^

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

sbtでプログラムのコンパイルと実行

今日はいわゆる Hello, world プログラムを作って実行してみる。HelloWorld.scala ファイルはこんな感じ。

object HelloWorld {
    def main(args: Array[String]): Unit = {
        println("Hello, World!")
    }
}

なんとなくわかるけど、細かく見るとわからないところもある。ま、今日のところはこんなふうに書くんだと思っておく。

で、もう一つ、sbt で実行するときには build.sbt ファイルというのを作る。これは sbt の設定ファイルだと考えておけばよさそう。

scalaVersion := "2.12.7"

scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")

さて、これをコンパイル、実行する。当然 sbt を使う。次のようにして sbt を起動して、

^o^ > sbt

run コマンドを打ち込む。すると、sbt は main メソッドを持つファイルを探してコンパイルし、実行してくれる。次のようになった。

sbt:scala> run
[info] Running HelloWorld
Hello, World!
[success] Total time: 0 s, completed 2019/05/03 18:45:44

なんかいろいろ情報が表示されてるけど、「Hello, World!」って出力されてるからうまくいったようだ。

データバックアップサーバ

データのバックアップサーバを作った。これまでは各マシンに外付け HDD をつけてそこにバックアップしてたけど、バックアップサーバに一元化することにした。バックアップサーバには新しく買った 6TB の外付け HDD を取り付けた。

バックアップの方法はこれまで通り rsync を使うんだけど、ネットワーク越しにバックアップサーバにバックアップすることになる。

具体的な手順は過去記事に譲ろう。

順次バックアップ設定を変えていって、バックアップサーバに集約する。

とすると、さて、外付け HDD が4つ余ることになるんだけど、どうしようか……。

値と変数、型推論

昨日、Scala のインストールが終わったので、今日から使ってみよう。

適当なフォルダに移動して sbt console コマンドで REPL を起動する。そして “Hello, World!” と入力してみる。

scala> "Hello, World!"
res0: String = Hello, World!

1行目が REPL のプロンプトと入力、2行目がそのレスポンスだ。ここでは入力された値が String 型で Hello, World! という値を持つことが表示されている。res0 ってのは REPL が勝手につけた名前らしい。これは後から使うこともできる。

scala> res0
res1: String = Hello, World!

いくつか値を入力してみよう。

scala> 123
res2: Int = 123

scala> 12345L
res3: Long = 12345

scala> 1.23
res4: Double = 1.23

scala> 1.23e4
res5: Double = 12300.0

scala> 1.0 == 1
res6: Boolean = true

それぞれ Int とか Double とかの型に解釈されている。Scala には型推論の機能が備わっているので、型を宣言しなくても適当な型に解釈してくれる。

次、変数。Scala では変数は宣言してからでないと使えない。変数の宣言には var と val の2つがあって、前者は再代入が可能だけど、後者は再代入できない。試してみよう。

scala> var x = 3
x: Int = 3

scala> x = 5
x: Int = 5

scala> x
res7: Int = 5

scala> val y = 2
y: Int = 2

scala> y = 7
<console>:12: error: reassignment to val
y = 7
^

var で宣言した x には再代入できているけど、val で宣言した y に再代入したらエラーになっているのがわかる。

Scala では基本的に再代入できない val で宣言した変数を使い、var はあまり使わないとのこと。このへんは関数型プログラミングらしいところかな。

ちなみに、var で宣言した x に再代入することはできるけど、宣言の時に型推論が働いて x の型は Int になっている。なので Int 以外の型を代入することはできない。

scala> x = "hello"
<console>:12: error: type mismatch;
found : String("hello")
required: Int
x = "hello"
^

さて、ここまでは変数の型は型推論に任せてきたけど、明示的に宣言することもできる。次のようにする。

scala> val z: Int = 3 * 3
z: Int = 9

型を変数名の後につけるのは Go に似てるな。

以上、とりあえずここまで。

sbtとScalaのインストール

昨日は JDK のインストールをしたので、今日は Scala をインストールする。

といっても、通常は Scala を直接インストールするのではなく、sbt というツールを使うのが普通のようだ。そこで、まずは sbt をインストールする。

 cf. sbt – Download

上のダウンロードページから、最新版(1.2.8)の msi インストーラをダウンロード。ダブルクリックするとインストールが始まる。基本的に [Next] ボタンをクリックしていけば完了する。

sbt のインストールが完了したら、適当なフォルダを掘ってコマンドラインで sbt console コマンドを起動してみよう。こんなメッセージが出て入力待ちの状態になる。

^o^ > sbt console
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
[info] Updated file C:\Users\takatoh\Documents\sandbox\scala\project\build.properties: set sbt.version to 1.2.8
[info] Loading project definition from C:\Users\takatoh\Documents\sandbox\scala\project
[info] Updating ProjectRef(uri("file:/C:/Users/takatoh/Documents/sandbox/scala/project/"), "scala-build")…
[info] Done updating.
[info] Set current project to scala (in build file:/C:/Users/takatoh/Documents/sandbox/scala/)
[info] Updating …
[info] Done updating.
[info] Non-compiled module 'compiler-bridge_2.12' for Scala 2.12.7. Compiling…
[info] Compilation completed in 6.282s.
[info] Starting scala interpreter…
Welcome to Scala 2.12.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_201).
Type in expressions for evaluation. Or try :help.
scala>

これは Scala の REPL で、対話的に実行できる環境。Ruby の irb みたいなものだ。「Welcome to Scala 2.12.7」と出ているので、Scala 2.12.7 がインストールされたことがわかる。

REPL を終了するには次のようにする。

scala> :quit

次回からはこの REPL を使って、Scala を触ってみることにする。