Help us understand the problem. What is going on with this article?

第15章:Scalaの制御構造

More than 5 years have passed since last update.

最近、型り(語り)まくっていたけど、今回は制御構造だよ。

Scalaではどんな制御構造があるのでしょうか?

Scalaの制御構造

処理をどのように実行するかを決めること

制御構造をザックし言ってみました。
これを実現するために、多くのプログラミング言語ではifやfor等が用意されています。

Scalaの制御構造以下となっています。

  • if
  • for
  • while
  • try
  • match
  • 関数呼び出し

JVMを使っているからJavaと違いがないと思いきや
やっぱり別言語なので違いがありますね。。

今回は制御構造とあわせて、変数のスコープ等についても紹介していきます。

if

選択構造の代表です。 if式 と呼びます。
以下の記述は違和感なく読むことができると思います。

IfTest1.scala
import java.io.File

object IfTest1 {

  def main(args: Array[String]): Unit = {
    var filename = "config.txt"

    if (!args.isEmpty)
      filename = args(0)

    println("Filename:" + filename)
  }

}

ifの条件式 !args.isEmpty が true に成った場合、
つまりパラメータ指定で実行した時に filename の値を入れ替えます。
if の普通な使い方だと思います。

でもよーく見ると。これイケてないですよね?
変数がイミュータブルになっていないし、再代入しているし。

このような場合例えば Java だと、filename の確定処理をメソッドとして
切りだすと思うのですが、Scala での if は式のため、値を返すことができるのです。

では if が値を返すようにリファクタリングしたソースを見てみよう。

IfTest1Refactor.scala
import java.io.File

object IfTest1Refactor {

  def main(args: Array[String]): Unit = {
    val filename = if (args.isEmpty) "config.txt" else args(0)

    println("Filename:" + filename)
  }

}

変数 filename をイミュータブルに変更することができ
しかも値の確定処理を変数の宣言と共に1行にすることができました。

if を使うと、宣言と値の定義が1箇所に纏まって見やすくなりましたね。

for

繰返構造の代表です。所謂ループってやつです。
for式 と呼びます。

まずは基本的な使い方から。
コンソールで試してみます。

scala> val list = List("Brad Jones", "Steven Gerrard", "Daniel Agger", "Daniel Sturridge")
list: List[java.lang.String] = List(Brad Jones, Steven Gerrard, Daniel Agger, Daniel Sturridge)

scala> for (name <- list) { println(name) }
Brad Jones
Steven Gerrard
Daniel Agger
Daniel Sturridge

Java の拡張 for 文と似ていますね。

ここから for 式のパワーを見てみましょう。

フィルタリング

Java と違って、Scala ではフィルタリングが簡単に実現できます。

scala> for (name <- list if name.startsWith("Daniel")) { println(name) }
Daniel Agger
Daniel Sturridge

Daniel で始まる文字列だけでフィルタリングしてみました。
Java だと Commons Collections を用意したりで、面倒くさくなります。

試しにCommons Collections を使って上のソースと
同じモノをJavaで実現してみました。

JavaFiltering.java
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.iterators.FilterIterator;
import org.apache.commons.lang3.StringUtils;

public class JavaFiltering {

    public static void main(String[] args) {
        final List<String> list = Arrays.asList("Brad Jones", "Steven Gerrard",
                "Daniel Agger", "Daniel Sturridge");

        final Iterator<String> iterator = new FilterIterator(list.iterator(),
                new Predicate() {

                    @Override
                    public boolean evaluate(Object object) {
                        final String name = (String) object;
                        return StringUtils.startsWith(name, "Daniel");
                    }
                });

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

}

Scalaだとこの1行ですが

for (name <- list if name.startsWith("Daniel")) { println(name) }

Javaだと、

final Iterator<String> iterator = new FilterIterator(list.iterator(),
        new Predicate() {

            @Override
            public boolean evaluate(Object object) {
                final String name = (String) object;
                return StringUtils.startsWith(name, "Daniel");
            }
        });

while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

のように、 Predicate でフィルタリング条件を作って
フィルタリングする Iterator 作ったりでかなり長くなってしまいました。
完全に冗長に書いてはいますけどね。

Scala は簡単過ぎます!

新しいコレクションの生成

フィルタリングでは繰返の中で値を使いましたが
フィルタリング結果を新しいコレクションにすることも可能です。

scala> val daniels = for { name <- list if name.startsWith("Daniel") } yield name
daniels: List[java.lang.String] = List(Daniel Agger, Daniel Sturridge)

scala> for (name <- daniels) { println(name) }
Daniel Agger
Daniel Sturridge

ここで yield が登場しています。
for は戻り値がないですが、yield を使うと演算結果をコレクションにして
返すことができます。

while

これも繰返構造の代表ですね。
Scala での使い道がわかっていないので
サラッと紹介します。

whileループ

scala> var count = 0;
count: Int = 0

scala> while (count < 5) {
     |   count += 1
     |   println(count)
     | }
1
2
3
4
5

while の条件式が true である間、コードブロックを実行します。

do-while ループ

まずはコードからです。

scala> var count = 0;
count: Int = 0

scala> do {
     |   count += 1
     |   println(count)
     | } while (count < 5)
1
2
3
4
5

whiledo-while の違いは、
while は条件式の評価を先にします。
do-while はコードブロックを先に実行し、条件式を評価します。

変数のスコープ

変数の生存範囲ですね。
変数は基本的には中括弧 {} 内でのみ生存できます。

ある生存範囲では、同じ変数を宣言できません。
なので次の場合は、エラーとなります。

scala> {
     |   val variable = "a"
     |   val variable = "b"
     | }
<console>:10: error: variable is already defined as value variable
                    val variable = "b"
                        ^

そして、外側の中括弧で宣言した変数は、その内部の中括弧でも
使用可能です。以下は、外側の中括弧で宣言した変数 count を、
while の中括弧内で使用しています。

scala> {
     |   val variable = "a"
     |
     |   {
     |     println(variable)
     |   }
     | }
a

ここまではJava と変わらないんだけど、最後に「Scala 固有の規則」です。

Scala では、そして、外側の中括弧で宣言した変数は、
その内部の中括弧でも 宣言 可能です。

scala> {
     |   val variable = "a"
     |
     |   {
     |     val variable = "b"
     |     println(variable)
     |   }
     | }
b

この時、 内部の変数は外側の変数をシャドウイングしている と表現します。

実はコンソール上の動きは、シャドウイングしてたりします。

ただこのシャドウイング、ソースを書く上では全く使い道がわからないです。
スコープの小さい if 式とかで、変数名を考えるのが面倒臭い時等に使うくらいかな。

読み辛くなる可能性が高いので、あまり使わない方が良いですね。

例外 (Exception)

例外とは、 処理の異常時に戻り値とは別のやり方で呼出元に通知する仕組み です。
呼出元は例外発生の通知を受け取った時に、以下なことをするのが多いです。

  • 上位に更に通知を出す(上位に伝播させる)
  • 別の処理(所謂、準正常系や異常系の処理)を実施する

例外を作る (throw)

例外を作るのは簡単です。
以下のようにするだけです。

throw new FileNotFoundException

これで FileNotFoundException という例外を生成して、上位に投げています。
スローすると言ったりします。

実際に試してみたのが以下となります。

scala> throw new java.io.FileNotFoundException
java.io.FileNotFoundException
        at .<init>(<console>:8)
        at .<clinit>(<console>)
        at .<init>(<console>:11)
        at .<clinit>(<console>)
        at $print(<console>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
        at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
        at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
        at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
        at java.lang.Thread.run(Thread.java:722)

これで自分で例外の生成ができるようになりますね!

例外を捕える (try/catch)

次に、下位でスローされた例外の通知を検知できるようにします。
例外をキャッチすると言ったりします。

try/catch 節というのを使います。
ソースをみてみましょう。

コンソールで動かした結果も載せたいので
パッケージ名載せて見辛くなっているはゴメンナサイ。

scala> try {
     |   val input = new java.io.FileInputStream("input.txt")
     | } catch {
     |   case ex: java.io.FileNotFoundException => println("File not found")
     |   case ex: java.io.IOException => println("IOError")
     | }
File not found

例外クラス

まず2つの例外クラスについて説明します

  • java.io.FileNotFoundException

    • 実ファイルが存在しない場合にスローされる
  • java.io.IOException

    • 書き込みが失敗する等のIO系のエラーが発生した場合にスローされる

java.io.FileInputStream のインスタンスを作った時に
この2つの例外がスローされると予測しています。

catch

例外をキャッチします。
そしてcaseでパターンマッチを行っています。

このソースを実行した時に、「input.txt」というファイルが存在していなかったので
「File not found」と出力されました。

try

どこで発生した例外を対象にするかを決めます。
try で始まる中括弧の中で発生した例外がのみが、
キャッチ対象となるわけです。

finally

例外がスローされると後続の処理が実施されません。
正常系でも例外が発生しても、最後に確実に実行させたい処理がある場合に使用します。

scala> {
     |   val input = new java.io.FileInputStream("input.txt")
     |
     |   try {
     |     throw new java.io.IOException // 明示的に例外をスロー
     |
     |     input.close() // 例外がスローされているので、ここには到達しない
     |   } catch {
     |     case ex: java.io.FileNotFoundException => println("File not found")
     |     case ex: java.io.IOException => println("IOError")
     |   } finally {
     |     println("Finally")
     |     input.close()  // 例外が発生しても、しなくてもここに入ってくる
     |   }
     | }
IOError
Finally

よくあるのがファイルのクローズですね。

Scalaの例外

最後に、Scala ではJava と違ってチェック例外のキャッチが必須ではないです。
チェック例外って何って?

Javaの例外には2種類あります。

  • チェック例外
  • 非チェック例外

チェック例外

Exception クラスをを継承しているクラスをスローして発生させる例外。
throws を強制される。

上位では以下のいずれかが必要となる。

  • キャッチする
  • throws 宣言をして、更に上位にスローする

非チェック例外

RuntimeException クラスを継承しているクラスをスローして発生させる例外
throws を強制されない。

上位でのキャッチや throws 宣言は必須ではない。

つまりScala では、Java における 非チェック例外 のように例外を扱えます。

まとめ

制御構造にしてみたけどどうだった?

今回、 match については触れなかったけど、別の機会で語ります。

今回も
体で感じてくれたかな?

f81@github
Fringe81のエンジニアが頑張って執筆ちゅうです! Scala の修行を始めました。 みなさま、温かい目で見守ってください。
fringe81
Fringeは、最新のテクノロジーとプロフェッショナルによるサービスにより、社会課題に仮説を立てて市場に広げていくことで、数十年という長期的なスパンで価値を生み出し続け、より良い世界を創る集団です。 既存の領域に限らず、時流を読み、仮説を生み出し、テクノロジーの力で優れたサービスを生み出し続けます。
https://www.fringe81.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away