ゆるふわコップ本読書会第7章
Scalaの基本制御構造はごく小規模だが、命令形言語の基本
機能を全て提供できるだけの力を持っている。そして一貫し
て結果値を返すことによって、コードの短縮化を実現する。
P130
はじめに
・Scalaの制御構造(if,while,for,,,)は、関数型へのアプローチ
としてほとんどが何らかの値を返します。
・関数型言語ではプログラムは値を計算する存在とみなしま
す。
if式
if (条件式) A [else B]
条件式:Boolean型である必要がある
else B:省略可能(省略した場合、下記と同価)
if(条件式) A else ()
条件式がfalseの時
Unit型が返ってくる
Scalaのifは、他の多くの言語と同じように機能します。
次は具体例
if式の具体例
val filename = if (!augs.isEmpty) args(0) else “default.txt”
ポイント
・valを使用する事より、読解時、変数の値が不変の為、変数
のスコープ内を全チェックして値の変化を調べる必要が無い。
・valを使用する事より、等式推論(変数と計算式を等価とみな
す)がサポートされ、変数名の代わりに計算式を書ける。
println (if (!args.isEmpty) args(0) else “default.txt”)
条件式 trueの時 falseの時
値を返す
whileループ
while (条件式) A
条件式:Boolean型である必要がある
条件式がtrueの間、Aを評価し続けます。
whileも式なので値を返しますが、
返却値はUnit型の値()を返します。
※whileは意味のある値を返さず、Unit型を返すた
め、式では無く「ループ」と呼ばれます。
whileループの具体例
while (条件式) A
var count = 0
while (count < 10) {
println(count)
count += 1
}
注意点
・while自体は値を返さない為、
計算処理をする時は、varを更新す
るか、入出力処理を実行する必要
がある。
つまり!!、
whileを使う部分では、変数の値の
変化がある為、while自体非推奨
なので、whileを使わない選択を
whileループを使わない
scala> def gcd(x:Long,y:Long):Long = if (y == 0) x else gcd(y,x % y)
gcd: (x: Long, y: Long)Long
scala> gcd(24,8)
res3: Long = 8
ループ処理をwhileを使わずに処理し、
以下のように再起(自分を呼び出す)を使用するのが一般的です。
条件式がfalse
になるまで繰り返すvarが出てこない!!
例:最大公約数を求める
whileループでよくあるエラー
var line = “”
while((line = readLine()) != “”)
println(“Read: ” + line)
コンパイルすると、「Unit型の値とString型の値を!=で比較す
れば、必ずtrueになる」という警告が表示されます。
※scalaでは、代入の結果値は常にUnit値()になります。
代入結果はUnit型
必ずtrue
String型
無限
ループ
for式
for(ジェネレータ1; ジェネレータ2; ... ジェネレータn) A
for(a1 <- exp1; a2 <- exp2; ... an <- expn) A
一般例
具体例
変数a1〜an:ループ変数
exp1~expn:式。例えば、ある数の範囲を表す式
※「1 to 10」や「1 until 10」
for式の具体例
for(x <- 1 to 3; y <- 1 until 4 if x != y){
println("x = " + x + " y = " + y)
}
x = 1 y = 2
x = 1 y = 3
x = 2 y = 1
x = 2 y = 3
x = 3 y = 1
x = 3 y = 2
各変数に対する計算式
出力結果
for式の具体例2
val fileHere = (new java.io.file(“.”)).listfiles
for( file <- filesHere )
println(file)
良い例
val fileHere = (new java.io.file(“.”)).listfiles
for( i <- 0 to fileHere.length - 1 )
println(filesHere(i))
駄目な例
Scalaではコレクションを直接反復処理できる。
その方がコードは短くなるし、
数値の境界値におけるエラー等を避けられる。
例:「0 to 9」 を 「1 to 10」にしてしまう等のエ
ラーを直接コレクションを弄る事により無くせる。
for式のフィルタリング
val fileHere = (new java.io.file(“.”)).listfiles
for(
file <- filesHere
if file.isFile // ファイルであるか?
if file.getName.endswith(“.scala”) // 末尾が”.scala”であるか?
)
println(file)
フィルタリング
if文を複数追加可能
for式の入れ子の反復処理
for(i <- 0 to 4 if i % 2 == 0;x <- 0 to 4 if x % 2 != 0){
println("i = " + i + "; x = " + x)
}
i = 0; x = 1
i = 0; x = 3
i = 2; x = 1
i = 2; x = 3
i = 4; x = 1
i = 4; x = 3
複数のコレクションi = 0に対して
x = 0 ~ 4
i = 2に対して
x = 0 ~ 4
...
変数への中間結果の束縛
for{
line <- fileLines(file)
if line.trim.matches(pattern)
} println(file + “: ” + line.trim)
for{
line <- fileLines(file)
trimed = line.trim
if trimed.matches(pattern)
} println(file + “: ” + trimmed)
line.trimが複数回呼ばれるので、
等号(=)を使って変数に結果を束
縛する。束縛というのは、等号の
左辺と右辺の式の結果値を紐付け
る。要は、この式の中だけで有効
な代入である。
新しいコレクションの作成
scala> var a = for{i <- 0 to 6 if i % 2 == 0} yield i
a: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 2, 4,
6)
今までの例では、反復的に生成された値をその場で使用した
だけでしたが、yieldを使い、値を保存する事ができます。
for {節} yield <本体>
一般例
具体例
生成された値
を格納していく
ifとforを使った練習問題
1から100までの3つの整数a, b, cについて、三辺からなる三
角形が直角三角形になるような a, b, cの組み合わせを全て出
力してください。直角三角形の条件にはピタゴラスの定理を
利用してください。 ピタゴラスの定理とは三平方の定理とも
呼ばれ、a ^ 2 == b ^ 2 + c ^ 2を満たす、a, b, c の長さの三辺
を持つ三角形は、直角三角形になるというものです。
要は、a^2 == b^2 + c^2
となるa,b,cの組み合わせを出力してください
練習問題のヒント
for(a <- 1 to 100; b <- 1 to 100; c <- 1 to 100) {
println((a, b, c))
}
単純に、a,b,cの組み合わせを出力するだけなら下記
for節の中に、a^2 == b^2 + b^2となる
条件をつけて下さい
練習問題の回答
for(
a <- 1 to 100;
b <- 1 to 100;
c <- 1 to 100 if a * a == b * b + c * c
) {
println((a, b, c))
}
条件を付ける
try式による例外処理
・メソッドは、通常の値で結果値を返すのでは無く、例外を
投げて終了する事ができる。
・メソッドの呼び出し元は、例外をキャッチして処理するか、
そのまま終了する事ができる。
・例外は、キャッチして処理するメソッドが現れるまで、呼
び出しスタックを巻き戻しながら呼び出し元から呼び出し元
へと例外が伝播していく。
try式による例外処理
try {
何らかの処理が失敗時に例外を投げる。
又は、明示的に例外を投げる
}
catch { //例外を受ける所
case ex: Exception => // 例外の処理
}
finally { //最後に必ず実行する処理を書く所
何らかの処理
}
try式による例外のスロー
scala> val n = 3
n: Int = 3
scala> val half = if (n % 2 == 1) {n / 2}
else {throw new RuntimeException("n must be even")}
half: Int = 1
scala> val half = if (n % 2 == 0) {n / 2}
else {throw new RuntimeException("n must be even")}
java.lang.RuntimeException: n must be even
... 27 elided
条件にマッチしたので例外投げない
条件にマッチしない
ので例外を投げる
値を返す
例外を返す
例外のキャッチ
import 色々
try{
val f = new FileReader(“input.txt”)
} catch {
case ex: FileNotFoundException => {ファイル無しerror処理}
case ex: IOException => {I/O error処理}
} finally {
file.close()
}
最後に必ず呼ばれる処理を書く。
例外が呼ばれた場合でも、
必ずファイルは閉じる。
例外のキャッチと処理
例外発生時の値の生成について
・例外が投げられない場合は、try節の値となる。
・例外が投げられて、catch節で処理した場合は、catch節の値
となる。
・例外が投げられて、catch節で処理されなかった場合は、結果
値を持たない。
・例外が投げられて、finally節が計算した値は、finallyの値で上
書きされる。その為、finally節では、一般的にtryやcatchの計
算された値を変更してはならない。 次にfinallyの副作用
直感に反する値の生成
scala> def f(): Int = try return 1 finally return 2
f: ()Int
scala> f()
res3: Int = 2
scala> def g(): Int = try 1 finally 2
g: ()Int
scala> g()
res4: Int = 1
finallyの値が返る
tryの値が返る
不思議?
例外の注意点1
Javaの検査例外も含めすべての例外が非検査例外の扱いになる。
JavaのAPIを呼び出す時にthrowされる例外を全て捕捉しなくて
もコンパイルエラーにはなりません。
Javaとの相互運用のために@throwsアノテーションを用いると、
throwsを付与したJavaバイトコードを生成することができます。
@throws(classOf[FooException])
@throws(classOf[BarException])
Javaとの相互運用時、Javaの例外をなげる場合
例外の注意点2
例外は非同期プログラミングでは使えない
例外の動作は送出されたらcatchされるまでコールスタックを遡
っていくというものです。ということは別スレッドや、別のイ
ベントループなどで実行される非同期プログラミングとは相容
れないものです。特にScalaでは非同期プログラミングが多用さ
れるので、例外をそのまま使えないことが多いです。
match式
マッチ対象の式 match {
case パターン1 [if ガード1] => 式1
case パターン2 [if ガード2] => 式2
case ...
case パターンN => 式N
}
scala> val one = 1
one: Int = 1
scala> one match {
| case 1 => "one"
| case _ => "other"
| }
res0: String = one
具体例
一般例
1にmatchしたらoneを返す。
それ以外ならotherを返す。
match式
・Scalaのcaseでは、Javaのcase文とは異なり、整数型や文字
列型だけでは無く、あらゆる型を扱えます。
・Scalaでは、breakが暗黙のうちに指定され、上の選択肢から
下の選択肢に制御が落ちる事はありません。
・Javaのswitchとの大きな違いは、match式が結果値を生成す
る事である。
breakとcontinueは無い
var i = 0
var foundIt = false
while (i < args.length && !foundIt) { // 引数の方が長く、見つかって無
い
if (!args(i).startsWith(“-”)) { // 文頭が”-”ではない
if (args(i).endWith(“.scala”)) // 文末が”.scala”である
foundIt = true // 見つかった!!
}
i = i + 1
} 命令型の書き方だと
breakとcontinueは無い
def searchfrom(i: Int): Int =
if (i >= arts.length) -1
else if (args(i).startsWith(“-”)) searchFrom(i + 1)
else if (args(i).endWith(“.scala”) i
else searchFrom(i + 1)
val i = searchFrom(0)
文頭が”-”なので、
iに1を足して最チャレンジ(再帰)
文末が”.scala”では無いので、
iに1を足して最チャレンジ(再帰)
ループ処理は、
再帰処理とvalのみを使う
変数のスコープ
val a = 1;
{
println(a)
}
1と表示される
val a = 1;
{
val a = 2
println(a)
}
println(a)
2
1
と表示される
val a = 1;
{
println(a)
val a = 2
println(a)
}
error
変数のスコープは{}等で切り替わる。
※中括弧以外で切り替わるのある??
forward
reference
extends over
definition of
value a
命令形から関数型へ1
scala> def printArgs(args: Array[String]): Unit = {
| var i = 0
| while (i < args.length) {
| println(args(i))
| i += 1
| }
| }
printArgs: (args: Array[String])Unit
scala> printArgs(Array("zero", "one", "two"))
zero
one
two
標準出力という副作用
varは控えよう!
命令形から関数型へ1
・whileを使う部分では、
変数の値の変化がある為、while自体非推奨
・varでは無く、valを使おう!!
scala> def printArgs2(args: Array[String]): Unit = {
| for (arg <- args)
| println(arg)
| }
printArgs2: (args: Array[String])Unit
scala> printArgs2(Array("zero", "one", "two"))
zero
one
two
varを無くし、
whileからforへの変更
Unit型
命令形から関数型へ2
返り値のデータ型が Unit型 のメソッドは、実行すると副作用
を引き起こすメソッドである。
副作用 を伴う println()メソッド の呼び出し処理を、「引数の
リストから要素を取り出して、標準出力に表示させたい文字
列を返す処理」として切り離し、副作用が生じる処理領域を
見つけやすいようする。
副作用の無い関数には、単体テストが簡単になるというメリ
ットもある。
命令形から関数型へ2
scala> def formatArgs3(args: Array[String]) = args.mkString("¥n")
formatArgs3: (args: Array[String])String
scala> println(formatArgs3(Array("zero", "one", "two")))
zero
one
two
String型
String型を受取り、
受け取り手が標準出力を実施する。
if,try,matchの練習問題
入力された西暦が、閏年ならば「”LeapYear”」
閏年でないなら「”NotLeapYear”」2000年なら
「”Millennium”」
を返すメソッドを作ってください。
受け取った値に応じて、「閏年です」や「閏年では無い」とい
う標準出力、又は「Problem」という例外を返してください。
★閏年の定義
・西暦年が4で割り切れる年は閏年
・ただし、西暦年が100で割り切れる年は平年
練習問題のヒント
閏年判定部分
(year % 400 == 0) || (year % 100 != 0 && year % 4 == 0)
match部分
val YearJudge = 閏年判定メソッド(2000)
try{
YearJudge match{
case “LeapYear” =>
・・・
}
} catch {
} finally {}
練習問題の回答
def isLeapYear(year: Int) = {
if (year == 2000) {
"Millennium"
} else if ((year % 400 == 0) || (year % 100 != 0 && year % 4 == 0)) {
"LeapYear"
} else {
"NotLeapYear"
}
}
2枚目に続く
練習問題の回答
val YearJudge = isLeapYear(2000)
try{
YearJudge match {
case "LeapYear" => println("閏年です")
case "Other" => println("閏年では無い")
case "Millennium" => throw new Exception("Problem")
}
} catch {
case ex: Exception => println("Millennium catch")
} finally {
println("finish")
}
まとめ
・Scalaに組み込みの制御構造は最小限のものだが、
必要な仕事を見事にこなす。
・制御構造は対応する命令形言語の構文と良く似ているが、
ほとんどが結果値を返すので、
関数型スタイルもサポートする。
・次章では、関数リテラルについて説明されます!!

ゆるふわScalaコップ本読書会 第7章