ゆるふわScalaコップ本読書会 第7章
-
Upload
yuta-yokoi -
Category
Technology
-
view
121 -
download
0
Transcript of ゆるふわScalaコップ本読書会 第7章
ゆるふわコップ本読書会第 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 (条件式) Avar count = 0while (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 = 2x = 1 y = 3x = 2 y = 1x = 2 y = 3x = 3 y = 1x = 3 y = 2
各変数に対する計算式出力結果
for 式の具体例 2val fileHere = (new java.io.file(“.”)).listfilesfor( file <- filesHere ) println(file)
良い例
val fileHere = (new java.io.file(“.”)).listfilesfor( i <- 0 to fileHere.length - 1 ) println(filesHere(i))
駄目な例
Scalaではコレクションを直接反復処理できる。その方がコードは短くなるし、数値の境界値におけるエラー等を避けられる。例:「 0 to 9 」 を 「 1 to 10」にしてしまう等のエラーを直接コレクションを弄る事により無くせる。
for 式のフィルタリングval fileHere = (new java.io.file(“.”)).listfilesfor( 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 = 1i = 0; x = 3i = 2; x = 1i = 2; x = 3i = 4; x = 1i = 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 ia: 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 = 3n: 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 2f: ()Int
scala> f()res3: Int = 2
scala> def g(): Int = try 1 finally 2g: ()Int
scala> g()res4: Int = 1
finallyの値が返る
tryの値が返る
不思議?
例外の注意点 1Javaの検査例外も含めすべての例外が非検査例外の扱いになる。 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 = 1one: 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 = 0var foundIt = falsewhile (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)21と表示される
val a = 1;{ println(a) val a = 2 println(a)}
error
変数のスコープは {} 等で切り替わる。※ 中括弧以外で切り替わるのある??
forward reference extends over definition of value a
命令形から関数型へ 1scala> 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"))zeroonetwo
標準出力という副作用
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"))zeroonetwo
var を無くし、while から for への変更
Unit 型
命令形から関数型へ 2 返り値のデータ型が Unit 型 のメソッドは、実行すると副作
用を引き起こすメソッドである。
副作用 を伴う println() メソッド の呼び出し処理を、「引数のリストから要素を取り出して、標準出力に表示させたい文字列を返す処理」として切り離し、副作用が生じる処理領域を見つけやすいようする。
副作用の無い関数には、単体テストが簡単になるというメリットもある。
命令形から関数型へ 2scala> def formatArgs3(args: Array[String]) = args.mkString("\n")formatArgs3: (args: Array[String])String
scala> println(formatArgs3(Array("zero", "one", "two")))zeroonetwo
String型
String型を受取り、受け取り手が標準出力を実施する。
if,try,match の練習問題”入力された西暦が、閏年ならば「 LeapYear” 」
”閏年でないなら「 NotLeapYear” 」 2000年なら”「 Millennium”」
を返すメソッドを作ってください。受け取った値に応じて、「閏年です」や「閏年では無い」という標準出力、又は「 Problem 」という例外を返してください。
★閏年の定義・西暦年が 4で割り切れる年は閏年・ただし、西暦年が 100 で割り切れる年は平年・ただし、西暦年が 400 で割り切れる年は閏年
練習問題のヒント閏年判定部分(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に組み込みの制御構造は最小限のものだが、 必要な仕事を見事にこなす。
・制御構造は対応する命令形言語の構文と良く似ているが、 ほとんどが結果値を返すので、 関数型スタイルもサポートする。
・次章では、関数リテラルについて説明されます!!