コップ本をやる 第7章 組み込みの制御構造

if式

Scalaのif式はJavaのif文と違い、値を返します。if式が値を返すことでより簡潔にコードを記述することができます。

val filename =
  if (!args.isEmpty) args(0)
  else "default.txt"

上記の例ではvarではなくvalが使えるのもメリットです。
また、等式推論をサポートしやすくなります。
println(filename)と書く代わりに、println(if (!args.isEmpty) args(0) else “default.txt”)と書くことができます。

(これはメリットなのか??コンパイラが一時変数を使わないような最適化を行うことはできるだろうけど、コード的には見づらいだけのような。)

whileループ

ScalaにもJavaや他の言語でおなじみのwhileが使えます。

def gcdLoop(x: Long, y: Long): Long = {
  var a = x
  var b = y
  while (a != 0) {
    val temp = a
    a = b % a
    b = temp
  }
  b
}

do-whileもあります。

do {
  line = readLine()
  println("Read: " + line)
} while (line != null)

whileとdo-whileは式では無くループです。式では無いのでUnitを返します。

Unitについて

Javaで言う所のvoidですが、voidと違いUnitはUnit値という値があります。
Unit値は()で表現します。

scala> def greet() { println(“Hello”) }
greet: ()Unit

scala> greet() == ()
Hello
res0: Boolean = true

()自体は変数名として認識される?

whileは純粋関数型言語では無い場合がありますが、Scalaでは互換性のために用意されています。
ただ、whileは使わずより関数型言語ぽく書く方が良い場合が多いでしょう。上記のgcdLoopは次のように書けます。

def gcd(x: Long, y: Long): Long =
  if (y == 0) x
  else gcd(y, x % y)

for式

Scalaのfor式はJavaのfor文とはだいぶ異なります。(より高機能になっています!) for式はコレクションに対する処理を簡潔に書くことができます。

val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere)
  println(file)

file <- filesHereの部分はジェネレータ(generator)と呼び、filesHereから一つずつ取り、valなfileに代入します。filesHereがArray[File]型なのでfileもFile型となります。

よくある1から10までのループも1 to 10とRangeを作れば可能です。

for (i <- 1 to 10)
  println("i=" + i)

添字のための0からsize-1までのループは 0 to size -1ではなく、0 until sizeとすることで可能です。

for (i <- 0 until size)
  println("list(" + i + ")= "+list(i))

フィルタリング
リストをフィルタリングしたい場合はfor式の中に条件を書くことができます。

for (file <- filesHere if file.getName.endsWith(".scala"))
  println(file)

複数のフィルタを同時に使うこともできます。

for (
  file <- filesHere
  if file.isFile
  if file.getName.endsWith(".scala"))
  println(file)

入れ子の反復処理
for式に複数の<-節を記述するとループを入れ子にすることができます。

for (
  file <- filesHere
  if file.getName.endsWith(".scala")); // <- ここにセミコロンが必要!
  line <- fileLines(file)
  if line.trim.matches(pattern)
) println(file + ": " + line.trim)

上記のfor式では括弧ではなく中括弧を使うことでセミコロンが必要という罠を回避できます。

for {
  file <- filesHere
  if file.getName.endsWith(".scala")) // <- 中括弧はセミコロンが必要無い
  line <- fileLines(file)
  if line.trim.matches(pattern)
} println(file + ": " + line.trim)

for式の中での束縛
for式の中で変数を定義することも可能です。

for {
  file <- filesHere
  if file.getName.endsWith(".scala")) // <- 中括弧はセミコロンが必要無い
  line <- fileLines(file)
  trimmed = line.trim
  if trimmed.matches(pattern)
} println(file + ": " + trimmed)

for式の中の変数定義はval変数となります。
(Scala的に束縛と変数定義は同義?)

for式の結果で新しいコレクションを作成

for式の本体の前にyieldキーワードを記述すると、その本体の戻り値をコレクションにする事ができます。

def scalaFiles =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala"))
  } yield file

scalaFilesの型はfileがFile型なので、Array[File]になります。
for式の本体をブロックにすることももちろん可能です。
また次の例ではArray[File]でループして、Array[String]を生成しています。

def scalaFileNames =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala"))
  } yield {
    val filename = file.getName
    filename
  }

for式はさらに機能があるようなので、第23章でより詳細な解説があるようです。

try式による例外処理

ScalaにもJavaと同じように例外を投げることができます。

throw new IllegalArgumentException

また、throwはNothing型の結果を返します。
従って次のように記述することが可能です。

val half =
  if (n % 2 == 0)
    n / 2
  else
    throw new RuntimeException("n must be event")

例外のキャッチ

try {
  val f = new FileReader("input.txt")
} catch {
  case ex: FileNotFoundException => println("ファイルが無いよ!")
  case ex: IOException => println("なんかIOエラー")
}

Scalaの例外はJavaと違い、throw節で明示的に記述する必要や、try-catchの強制はありません。

finally節
Scalaにもfinallyがあります。

val file = new File("input.txt")
try {
  // ファイルを使う
} finally {
  file.close()
}

また、finally節も値を返します。しかしfinally節で値を返すことはせず、ファイルのcloseなど副作用のみにしておくのが良いでしょう。
次のコードは2を返します。
def f(): Int = try { return 1 } finally { return 2 }

しかし次のコードは1を返します。(どういう理由で動作が違う?)
def g(): Int = try { 1 } finally { 2 }

match式

関数型言語のパターンマッチをScalaではmatch式で使うことができます。超高機能なswitchという感じです。

val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
  case "salt"  => println("pepper")
  case "chips" => println("salsa")
  case "eggs"  => println("bacon")
  case _       => println("huh?")
}

caseにはJavaと違いあらゆる型を指定することができます。
高機能なので15章で詳細に解説があります。

breakとcontinueはScalaには無い

そのような邪悪なものはScalaには必要無いと判断されたようです。
(実際には関数リテラルとの絡みで何らかの問題があるようだ)

continueはフィルタやif式を、breakはフラグを用いると言うので解決できます。

変数のスコープ

基本的にJavaと同じです。
ただ、変数のシャドウイング(shadow)が可能です。

val a = 1
{
  val a = 2
  println(a)
}
println(a)

Javaでは上記のようなコードは変数の多重定義でエラーですが、Scalaでは動作します。
2に続いて1を出力します。

No Comments

Post a Comment

コメントを投稿するには、下の計算の答えを入力する必要があります。答えは半角数字で入力してください。 * Time limit is exhausted. Please reload the CAPTCHA.