最近、型り(語り)まくっていたけど、今回は制御構造だよ。
Scalaではどんな制御構造があるのでしょうか?
Scalaの制御構造
処理をどのように実行するかを決めること
制御構造をザックし言ってみました。
これを実現するために、多くのプログラミング言語ではifやfor等が用意されています。
Scalaの制御構造以下となっています。
- if
- for
- while
- try
- match
- 関数呼び出し
JVMを使っているからJavaと違いがないと思いきや
やっぱり別言語なので違いがありますね。。
今回は制御構造とあわせて、変数のスコープ等についても紹介していきます。
if
選択構造の代表です。 if式 と呼びます。
以下の記述は違和感なく読むことができると思います。
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
が値を返すようにリファクタリングしたソースを見てみよう。
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で実現してみました。
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
while
と do-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 については触れなかったけど、別の機会で語ります。
今回も
体で感じてくれたかな?