Posted at

第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 については触れなかったけど、別の機会で語ります。

今回も

体で感じてくれたかな?