08.Scala の制御構文　Scala基礎コース

# 目次

* 【講義】制御構文とは
* 【実習】{} 式について
* 【実習】if 式について
* 【実習】while 式について
* 【実習】for 式について

# 【講義】制御構文とは

今回は、ここまでで使ってきた

* {} 式
* if 式
* while 式
* for 式

以上 4 つの制御構文をより深く知り、完全に使いこなせるようになる。

そもそもここまで「構文」と「式」と「文」という用語が入り乱れて使われていて少し分かりづらいので、先にこの 3 つの用語の解説をする。

まず「構文 (Syntax) 」は、そのプログラミング言語内でプログラムが構造を持つためのルール。

多くの場合、プログラミング言語内で特別扱いされるキーワード、例えば `class` や `val` 、 `if` などが含まれ、正しいプログラムを構成するためのルールがある。

次に「式 (Expression) 」は、プログラムを構成する部分のうち、評価することで値になるもの。
例えば `1` や `1 + 2` 、 `"hoge"` など。
これらは評価することにより、数値や文字列の値になる。
また「評価」という言葉も何度か説明の中で出てきたが、この評価とは Scala の式をコンピュータが解釈して、値に引き換える処理のことをいう。

最後に「文 (Statement) 」だが、式とは対照的にプログラムを構成する部分のうち、評価しても値にならないもの。
例えば変数の定義である `val i = 1` は評価しても変数 `i` が定義され、 `i` の値が `1` になるが、この定義全体としては値を持たない。
よってこれは文。

Scala は他の C や Java などの手続き型の言語に比べて、文よりも式にする構文が多く採用されている。
これにより、変数などの状態をできるだけ排除したわかりやすいコードが書きやすくなっている。

# 【実習】{} 式について

ここから具体的な、構文についての説明をするので、 IntelliJ IDEA を起動してみよう。

起動すると Welcome to IntelliJ IDEA ダイアログが開かれるので、 New Project という項目を選択して New Project ダイアログを起動しよう。

Scala の sbt を選択して、 Next ボタンを押し、

| 設定項目 | 設定内容 |
|:-:|:-:|
| Project name | scala-grammar |
|  Project location | デフォルトで設定済み。`~/IdeaProjdects/scala-grammar` |
| Project SDK | `New` をクリック → JDK をインストールしたフォルダを選択。 `/Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home` できるだけ最新のものを利用しましょう。 |
| sbt version | 1.2.6 （選択肢にない場合は、最新のものを使いましょう） |
| Scala version | 2.12.7 |

以上で、その他のチェックボックスはすべてデフォルトの設定で、 Finish ボタンをクリックする。
最初のビルドが終わるまで数分かかるが、 IDEA の右下のインジケーターが落ち着くまで待とう。

無事見る度が終わったら、 Project ツールウィンドウの `scala-grammer/src/main/scala` フォルダを選択して、右クリックから New > Scala Class を選択し、

* Name を `ControlSyntax`
* Kind を Object

それぞれ選択して 'Enter' を押して、 `ControlSyntax.scala` に ControlSyntax オブジェクトを作成しておく。

```scala:src/main/scala/ControlSyntax.scala
object ControlSyntax {

}
```

以上のようなコードが自動生成される。
今後このオブジェクトのコードを編集しながら、文法を学んでいく。

まずはエディタを右クリックしてコンテキストメニューから Scala REPL... を選択する。

```scala:REPL
/Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home/bin/java ...
Welcome to Scala 2.12.7 (OpenJDK 64-Bit Server VM, Java 1.8.0_292).
Type in expressions for evaluation. Or try :help.

scala> 
```

このように REPL が表示されれば成功。

まず最初に学ぶのは、 {} 式。
`{}` のことを英語で brace という。
{} 式の一般形は、

```scala
{ exp1; exp2; ... expN; }
```

となる。
`exp1` から `expN` は式。
式が改行で区切られていればセミコロンは省略できる。
`{}` 式は `exp1` から `expN` を順番に評価し、 `expN` を評価した値を返す。

具体的な例で言うと、

```scala
{ println("A"); println("B"); 1 + 2 }
```

以上のような書き方になる。
では REPL で評価してみよう。
REPL での評価の仕方は、 Command + Enter 。

```scala:REPL
scala> { println("A"); println("B"); 1 + 2 }
A
B
res0: Int = 3
```

A と B が 1 つ目と 2 つ目の println 関数の呼び出しによりコンソール出力され、最終的に評価されたものは Int 型の 3 が `1 + 2` の評価結果として表示される。

このように、 {} 式は、最後の式が評価されて最後の値が返る。
Scala ではメソッドの定義を、

```scala
def foo(): String = {
  "foo"
}
```

のような形で定義することが一般的だが、ここでも {} 式が利用されている。

```scala
def bar(): String = {
  "foo"
  "bar"
}
```

以上のメソッドを呼び出した場合は、 "bar" という値が取得される。

実際に、 ControlSyntax オブジェクトに、上記メソッドを記述してみよう。

```scala:src/main/scala/ControlSyntax.scala
object ControlSyntax {
  def bar(): String = {
    "foo"
    "bar"
  }
}
```

記述ができたら、 Scala Console ツールウィンドウの左上の緑色の矢印と四角が表示されたボタンを押して、コンパイルされた ControlSyntax オブジェクトを読み込もう。

そして、 `ControlSyntax.bar` を評価してみよう。

```scala:REPL
scala> ControlSyntax.bar
res0: String = bar
```

以上のように表示されるのではないだろうか。
Scala ではメソッドの呼び出し時に、引数がない場合には、 `()` を省略してメソッドを呼び出すこともできる。

なお、 IDEA で編集していると上記の `"foo"` のところに灰色の波線が出てきて、スクロールバーの灰色の線のツールチップを確認すると、

```
Unused expression without side effects 
```

と書かれているのではないだろうか。
これは、 `"foo"` が利用されていない式であるという意味の警告。
IDEA ではコードを編集中に、コードの不要な部分があった場合もコンパイラーを通じて警告を出してくれる。

警告が出続けると気になってしまうので、以下のように bar メソッドを編集して、

```scala
  def bar(): String = {
    "bar"
  }
```

以上のように修正しておこう。

# 【実習】if 式について

次は if 式。
もう既に if 式を使ったコードを書いてきたが、より詳細に if 式の動作を学ぶ。

if 式の構文は以下のようになる。

```scala
if (条件式) A [else B]
```

条件式は Boolean 型である必要がある。
また else B 部分は省略可能。

ControlSyntax オブジェクトに、

```scala
  def printOver18(age: Int): Unit = {
    val message = if (age < 18) {
      "18歳未満です"
    } else {
      "18歳以上です"
    }
    println(message)
  }
```

以上のメソッドを追加して、 REPL で動作を確認してみよう。

以上は age を Int 型として引数で受け取る printOver18 メソッド。
もし age が 18 未満であれば message の値が "18歳未満です" となり、そうでなければ、 "18歳以上です" となる。
if 式は値を返す「式」であるため、このような動作となる。

REPL で評価すると、

```scala:REPL
scala> ControlSyntax.printOver18(20)
18歳以上です
```

以上のような動作をする。
以上のコードで、もし仮に、 else 句を消すとどのようになるのだろうか。
やってみよう。

printOver18 メソッドを以下のように編集する。

```scala
  def printOver18(age: Int) = {
    val message = if (age < 18) {
      "18歳未満です"
    }
    println(message)
  }
```

REPL を Scala REPL の左上にある Rerun 'Scala REPL' ボタンを押して、再度、このメソッド引数を 20 として実行してみよう。

REPL で上矢印ボタンを押すと過去の入力が呼び出される。

```scala:REPL
scala> ControlSyntax.printOver18(20)
()
```

以上のように表示されたのではないだろうか。
この `()` という値はなんなのだろうか。
これは Unit という型の `()` というオブジェクトの値となる。

Unit は「ユニット」と呼ぶ。
関数が意味のある値を返さないということを意味する型。
Java や C というプログラミング言語においては関数が何も戻り値を持たない場合に void 「ボイド」という関数の戻り値の型を指定することがあるが、それと同じもの。

else を省略した場合、この Unit 型の `()` という値が返されるようになっている。

実はこの printOver18 関数の戻り値は何もない。
そのため printOver18 も厳密に戻り値型を表記した関数のインタフェースで書くと、

```scala
  def printOver18(age: Int): Unit = {
    val message = if (age < 18) {
      "18歳未満です"
    } else {
      "18歳以上です"
    }
    println(message)
  }
```

以上のように記述できる。

`def printOver18(age: Int): Unit` が正確なメソッドのインタフェースとなる。
Rerun 後、再度 REPL で評価して、動作に問題ないことを試してみよう。

このように、メソッドの記述位には戻り値の型も記述してやるのが、行儀の良いインタフェースの記述法となる。

# 【実習】while 式について

次は while 式。

```scala
while (条件式) A
```

条件式が Boolean 型の true である限り A を評価し続ける。

REPL で、

```scala
var i = 1
while(i <= 10) {
  println("i = " + i)
  i = i + 1
}
```

以上を評価してみよう。

```scala:REPL
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
```

このように、 `i = 1` から `i = 10` までが出力されるのではないだろうか。

なお while 式は式とある通り、値を取得できる。
そして、先ほど説明した通り、式として評価した場合に取得される値は `()` となる。
常に `()` しか返さないため、 while 文として使うことがほとんど。

試しに以下のコードを、 SyntaxControl オブジェクトに書いて評価してみよう。

```scala
  def printWhileResult(): Unit = {
    var i = 0
    val result = while (i < 10) i = i + 1
    println(result)
  }
```

今回は、 `{}` を省略して書いてみた。
while 式と if 式も `{}` は、省略して記述できる。
REPL を Rerun して、 `ControlSyntax.printWhileResult` を評価してみよう。

```scala:REPL
scala> ControlSyntax.printWhileResult
()
```

以上のように結果は、 Unit 型の `()` という値が帰っていることが分かる。
また条件式をループ対象の処理の直後に行う。

```scala
do A while(条件式)
```

以上の do-while 式に関しても同様に Unit 型の値を返す。

# 【実習】for 式について

既に for 式を使ったループについては一旦使い方を学んだ。
for 式はかなり複雑なループ処理を記述したり、またコレクションの内容の変換にも利用できる構文となっている。
では、詳しく説明していく。

for 式の基本的な構文は以下のようになる。

```scala
for (ジェネレータ1;  ジェネレータ2; ... ジェネレータn) A
// ジェネレータ1 = a1 <- exp1; ジェネレータ2 = a2 <- exp2; ... ジェネレータn = an <- expn
```

変数 a1 ~ an までは好きな名前のループ変数を使うことができる。
実際に具体例を示すメソッドを ControlSyntax オブジェクトに追記してみよう。

```scala
  def doubleLoop(): Unit = {
    for (x <- 1 to 5; y <- 1 until 5) {
      println("x = " + x + " y = " + y)
    }
  }
```

以上は 1 から 5 までを変数 x に入れて実行する 1 つ目のループと、 1 から 4 までを変数 y に入れて実行するループの 2 つのループを記述した処理。

REPL を Rerun して、 `ControlSyntax.doubleLoop` を評価してみよう。

```scala:REPL
scala> ControlSyntax.doubleLoop
x = 1 y = 1
x = 1 y = 2
x = 1 y = 3
x = 1 y = 4
x = 2 y = 1
x = 2 y = 2
x = 2 y = 3
x = 2 y = 4
x = 3 y = 1
x = 3 y = 2
x = 3 y = 3
x = 3 y = 4
x = 4 y = 1
x = 4 y = 2
x = 4 y = 3
x = 4 y = 4
x = 5 y = 1
x = 5 y = 2
x = 5 y = 3
x = 5 y = 4
```

以上のように動作する。
二重ループにおいて x は 1 から 5 まで、 y は 1 から 4 まで ループ処理されていることが分かる。
`1 to 5` は REPL で評価をすると 1 から 5 までを表す Range という方になり、 `1 until 5` は 1 から 4 までを表す Range 型になる。

```scala:REPL
scala> 1 to 5
res1: scala.collection.immutable.Range.Inclusive = Range 1 to 5

scala> 1 until 5
res2: scala.collection.immutable.Range = Range 1 until 5
```

for はこのように Range 型の値を `;` で複数受け取り、多重ループを 1 つの for で書くことができる。

なお更に for に続く `()` の中には、 if 句を書いてそのループで実行する条件を指定できる。

```scala
  def doubleLoop(): Unit = {
    for (x <- 1 to 5; y <- 1 until 5 if x != y) {
      println("x = " + x + " y = " + y)
    }
  }
```

以上は `if x != y` を書き足して、 x と y の値が等しくなる場合のループ処理を取り除いてある。
このことをフィルタリングという。

上記のように `if x != y` を書き加える変更をした後、REPL を Rerun して、 `ControlSyntax.doubleLoop` を評価してみよう。

```scala:REPL
scala> ControlSyntax.doubleLoop
x = 1 y = 2
x = 1 y = 3
x = 1 y = 4
x = 2 y = 1
x = 2 y = 3
x = 2 y = 4
x = 3 y = 1
x = 3 y = 2
x = 3 y = 4
x = 4 y = 1
x = 4 y = 2
x = 4 y = 3
x = 5 y = 1
x = 5 y = 2
x = 5 y = 3
x = 5 y = 4
```

以上のようになり、

```scala
x = 1 y = 1
x = 2 y = 2
x = 3 y = 3
x = 4 y = 4
```

以上の出力処理が省かれていることが分かる。

また、 for 式は、コレクションの要素を 1 つずつ辿って何かの処理を行うことにも利用できる。

A, B, C, D, E の 5 つの文字列の要素からなるリストを辿って、すべてを出力する処理を書いてみよう。

以下のメソッドを ControlSyntax オブジェクトに書き足す。

```scala
  def collectionLoop(): Unit =
    for(e <- Seq("A", "B", "C", "D", "E")) println(e)
```

今回は、メソッドの定義にも {} 式を使わずに書いてみた。
メソッドの中の処理が少ない場合には、このように `{}` を利用せずに書くこともできる。

REPL を Rerun して、 `ControlSyntax.collectionLoop` を評価してみよう。

```scala:REPL
scala> ControlSyntax.collectionLoop
A
B
C
D
E
```

以上のように、コレクションの要素をそれぞれループして、値をコンソールに出力できた。

そして、 for 式は、既に存在しているコレクションの要素を加工して新しいコレクションを作ることもできる。
その際には `yield` というキーワードを利用する。

例えば、先ほどの A, B, C, D, E の 5 つの要素の先頭に Pre という文字列を付加するコレクションを作成する場合は、以下のように実装できる。

```scala
  def generateCollection(): Seq[String] =
    for(e <- Seq("A", "B", "C", "D", "E")) yield "Pre" + e
```

以上のメソッドを ControlSyntax に追加して Rerun の後、 `COntrolSyntax.generateColletion` を評価してみよう。

generateCollection のインタフェースの戻りの型の通り、取得されるのは Seq[String] という型のコレクションとなる。

```scala:REPL
scala> ControlSyntax.generateCollection
res0: Seq[String] = List(PreA, PreB, PreC, PreD, PreE)
```

以上のように評価される。
このように for 式は様々なコレクションのループ処理に使える他、 `yield` キーワードを用いることでコレクションの変換や、ループからのコレクションの作成などを行うことができるようになっている。

# まとめ

* {} 式では、最後の式が値として評価される
* if 式では、 else 句を省略した場合には、 if の条件式に合致しない場合には Unit 型の値が取得される
* Unit 型の `()` という値は、関数が意味のある値を返さないということを意味する
* for 式は、ジェネレーターに if 句を設定してループをフィルタリングできる
* for 式は、 yield キーワードを用いることで、コレクションの値を返すことができる

# 挑戦

## 初級

```scala
  def printIsBaby(age: Int, isSchoolStarted: Boolean): Unit = {
    val result = if ((age >= 1 && age <= 6) || !isSchoolStarted) {
      println("幼児です")
    } else {
      println("幼児ではありません")
    }
  }
```

## 中級

```scala
  def loopFrom0To9(): Unit = {
    var i = 0
    do {
      println(i)
      i += 1
    } while (i < 10)
  }
```

## 上級

```scala
  def printRightTriangles(): Unit =
    for (
      a <- 1 to 1000;
      b <- 1 to 1000;
      c <- 1 to 1000;
      if a * a == b * b + c * c
    ) println((a, b, c))
```