Articles Tagged with Scala

コップ本をやる 第8章 関数とクロージャー

メソッド

オブジェクトのメンバーとして関数を定義するとメソッドになります。

object LongLines {
  def processFile(filename: STring, width: Int) {
    val source = Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(filename, width, line)
  }

private def processLine(filename: String, width: Int, line: String) {
    if (line.length > width)
      println(filename + ": " + line.trim)
  }
}

ローカル関数

外部に公開しない関数として、privateな非公開メソッドがありますが、Scalaでは関数内だけで使用する関数内で定義する関数として、ローカル関数があります。
先ほどのコードは次のようになります。

  def processFile(filename: STring, width: Int) {
    def processLine(line: String) {
      if (line.length > width)
        println(filename + ": " + line.trim)
    }
    val source = Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(filename, width, line)
  }

ローカル関数からは外側の関数のパラメータにアクセス可能なので、processLineのパラメータはlineだけになっています。

一人前の存在としての関数

Scalaの関数はfirst-classな関数です。
関数リテラル(function literals)はあるクラスにコンパイルされ、実行時にインスタンス化すると値としての関数(functionvalues)になります。
関数リテラルはソースコード上だけの存在で、関数値(値としての関数)はオブジェクトとして存在します。

次はInt型を取り1加算する関数リテラルの例です。
(x: Int) => x + 1

値としての関数はオブジェクトなので、変数に格納することができます。変数に格納しても関数なので通常の関数のように呼び出せます。

scala> var increase = (x: Int) => x + 1
increase: Int => Int =

scala> increase(10)
res0: Int = 11

関数に複数行コードを入れたい場合、中括弧で囲みます。その関数が返す値はブロック内の最終行の式が生成する値です。
scala> var increase = (x: Int) => {
x + 100
x + 10
x + 1
}
increase: Int => Int =

scala> increase(100)
res2: Int = 101

Scalaの多くのライブラリは、この関数リテラルと値としての関数をうまく使えるよう設計されています。
たとえばすべてのコレクションにはforeashメソッドが定義されており、foreachメソッドは関数をとってそれぞれの要素についてその関数を呼び出します。
scala> val someNumbers = List(-11, 10, 0, 100)
someNumbers: List[Int] = List(-11, 10, 0, 100)

scala> someNumbers.foreach((x: Int) => println(“value=” + x))
value=-11
value=10
value=0
value=100

コレクションについては17章で詳細に説明があります。

関数リテラルの短縮形

関数リテラルのパラメータの型は省略可能です。
scala> someNumbers.filter((x) => x > 0)
res4: List[Int] = List(10, 100)

パラメータは(x: Int)ですが、(x)と書けます。

さらに、型推論が可能な場合はパラメータの括弧も省略できます。
scala> someNumbers.filter(x => x > 0)
res5: List[Int] = List(10, 100)

プレースホルダー構文

もっと関数リテラルを簡潔に書くことができます。
パラメータが関数リテラル内で一度しか使われない場合は、アンダースコアでパラメータを使うことができます。
scala> someNumbers.filter(_ > 0)
res6: List[Int] = List(10, 100)

ただし、プレースホルダー構文を使うには型推論が可能である必要があります。
型推論できない場合はエラーになります。

scala> val f = _ + _
:10: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
val f = _ + _
^
:10: error: missing parameter type for expanded function ((x$1: , x$2) => x$1.$plus(x$2))
val f = _ + _
^

そんな場合でも型を指定してあげるとOKです。
scala> val f = (:Int) + (:Int)
f: (Int, Int) => Int =

scala> f(1, 2)
res7: Int = 3

部分適用された関数

上記のアンダースコアは個別のパラメータの代わりとなるものです。
アンダースコアは他にも、パラメータ全体の代わりとして使うこともできます。

scala> someNumbers.foreach(println _) // パラメータ全体をアンダースコアに置き換えている。
-11
10
0
100

scala> someNumbers.foreach(println(_)) // 最初のパラメータをアンダースコアに置き換えている
-11
10
0
100

次の例では3つのパラメータの合計を返すsum関数を考えます。
scala> def sum(a: Int, b: Int, c: Int) = a + b + c
sum: (a: Int, b: Int, c: Int)Int

scala> val a = sum _
a: (Int, Int, Int) => Int =

scala> a(1, 2, 3)
res11: Int = 6

sum _とすると、0個のパラメータを部分適用した関数を返します。
そしてその自動生成された関数にパラメータを入れると元の関数を実行することができます。

内部的には3つのパラメータを取るapply関数が定義された関数オブジェクトを生成しており、a(1, 2, 3)はa.apply(1, 2, 3)を実行していることになります。
scala> val a = sum _
a: (Int, Int, Int) => Int =

scala> a.apply(1, 2, 3)
res15: Int = 6

次のようにしてパラメータの一部を埋めた状態で残りのパラメータを入力させる関数を作ることもできます。
この場合1つのパラメータを取るapply関数を定義した関数オブジェクトを生成しています。
scala> val a = sum(1, _: Int, 3)
a: Int => Int =

scala> a(2)
res12: Int = 6

もしforeachのように、関数呼び出しが必要とされている部分ではアンダースコアさえも省略できます。
scala> someNumbers.foreach(println)
-11
10
0
100

関数呼び出しが必要とされている場所以外でこの記法を使うとエラーになります。
scala> val a = sum
:11: error: missing arguments for method sum;
follow this method with `_’ if you want to treat it as a partially applied function

クロージャー

今までは関数は関数のパラメータだけを参照していましたが、関数の外の変数を参照することも可能です。

もちろん関数の外にも見つからなければエラーです。
scala> val a = (x: Int) => x + more
:10: error: not found: value more
val a = (x: Int) => x + more

変数が存在すれば参照可能です。
scala> val more = 10
more: Int = 10

scala> val a = (x: Int) => x + more
a: Int => Int =

関数から見ると、moreは自由変数(free varibles)で、xは束縛変数(bound variables)です。
束縛変数だけ使っている関数リテラルは閉じた項(closed terms)と呼び、自由変数を使っている関数リテラルは開いた項(open terms)と呼びます。

開いた項は、関数リテラルから関数値を作成するときに自由変数の束縛をつかみ、閉じます。そのためその関数値をクロージャー(closure)と呼びます。

クロージャーは自由変数の束縛をつかみます。
つまり値では無く変数をつかむので、クロージャー作成後の変数の変更は参照可能です。
また、クロージャーの中で変数に対して変更を加えると、クロージャーの外にも影響します。

scala> var sum = 0
sum: Int = 0

scala> someNumbers.foreach(sum += _)

scala> sum
res20: Int = 99

連続パラメータ

Javaで言う所の可変長引数です。
関数のパラメータの型の後ろに*を付けることで連続パラメータとなります。

scala> def echo(args: String) =
| for (arg <- args) println(arg)
echo: (args: String
)Unit

scala> echo(“hoge”,”fuga”,”foo”, “bar”)
hoge
fuga
foo
bar

連続パラメータは関数内ではパラメータ型のArrayとなります。

Arrayなのであれば連続パラメータとしてArrayを入力としてできそうですが、そのままではコンパイルエラーです。

scala> def list = List(“1”, “2”, “3”)
list: List[String]

scala> echo(list)
:13: error: type mismatch;
found : List[String]
required: String
echo(list)

パラメータに_*の型を指定すると連続パラメータとして入力可能になります。
scala> echo(list: _*)
1
2
3

名前付き引数

通常関数の引数は、順番通りにパラメータに設定されます。

scala> def func1(x: Int, y: Int) = println(s”x=$x, y=$y”)
func1: (x: Int, y: Int)Unit

scala> func1(10,20)
x=10, y=20

引数を指定する際にパラメータ名も指定すると、その順番を入れ替えることができます。
scala> func1(y = 10, x = 20)
x=20, y=10

名前付き引数は次のパラメータのデフォルト値と一緒に使います。

パラメータのデフォルト値

Scalaではパラメータにデフォルト値を設定することができます。

scala> def func2(x: Int = 10, y: Int = 20) = println(s”x=$x, y=$y”)
func2: (x: Int, y: Int)Unit

scala> func2()
x=10, y=20

一つ引数を指定すると、最初のパラメータのみ引数の値が設定されます。
scala> func2(100)
x=100, y=20

二つ目の引数だけ値を設定したい場合は名前付き引数を使います。
scala> func2(y = 100)
x=10, y=100

末尾再帰

Scalaは末尾再帰の最適化がされます。
末尾再帰の最適化をしない場合はscalaシェル実行時かscalacでのコンパイル時に-g:notailcallsをしていします。

ただ、Scalaの末尾再帰最適化は自分自身の関数を呼び出す際だけにされ、二つの関数を呼び合うような末尾再帰は最適化されません。
また、値としての関数を呼び出す際にも末尾再帰されません。

コップ本をやる 第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を出力します。

コップ本をやる 第6章 関数型スタイルのオブジェクト

この章ではイミュータブルなクラスを作成する方法を学びます。
作成するクラスは有理数(分数)を表すRationalクラスです。

// nとdはクラスパラメータで、その二つを取る基本コンストラクタ
class Rational(n: Int, d: Int) {

  // 事前条件を指定する
  // 事前条件に違反する場合IllegalArgumentExceptionを投げる。
  require(d != 0)

  // フィールド、メソッド定義以外を基本コンストラクタとしてコンパイルする
  private val g = gcd(n.abs, d.abs)

  // フィールド定義
  val numer: Int = n / g
  val denom: Int = d / g

  // 補助コンストラクタ
  // Scalaの補助コンストラクタでは、最初の処理で同じクラスの他のコンストラクタを呼ぶ必要がある
  def this(n: Int) = this(n, 1)

  // オーバーライドする際はメソッド定義にoverride修飾子を付ける
  override def toString = n + "/" + d

  // 有理数の加算
  def + (that: Rational): Rational =
    new Rational(
      numer * that.denom + that.numer * denom,
      denom * that.denom)

  // 有理数の乗算
  def * (that: Rational): Rational =
    new Rational(
      numer * that.numer, denom * that.denom)

  // メソッドの多重定義
  def * (i: Int): Rational =
    new Rational(numer * i, denom)

  // thisで自己参照する
  def lessThan(that: Rational) =
    this.numer * that.denom < that.numer * this.denom

  // 非公開のメソッド
  private def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a % b)
}

object Main {
  def main(args: Array[String]): Unit = {
    val r1 = new Rational(10, 15)
    val r2 = new Rational(9, 15)
    println("print "+r1)
    println("r1 + r2 = "+ (r1 + r2))
    // *は+よりも優先度が高い
    println("r1 + r2 * r2 = "+ (r1 + r2 * r2))
    println("r1 < r2 ? "+ (r1 lessThan r2))
    println("r2 < r1 ? "+ (r2 lessThan r1))

  }
}

Scalaの識別子

Scalaの識別子には、英数字識別子、演算子識別子、ミックス識別子、リテラル識別子の4つがあります。

英数字識別子

先頭が英字かアンダースコアで、その後に英字、数字、アンダースコアが続きます。$も英字に含まれますが$はScalaコンパイラのために予約されているので使わないでおきましょう。

演算子識別子

一つ以上の演算子文字からなる識別子です。たとえば+や:、?、~、#などです。一つ以上なので、:::や<?>、:->も有効な演算子識別子です。

ミックス識別子

ミックス識別子はその名の通り英数字識別子と演算子識別子両方を含む識別子です。たとえばunary_+は単項演算子+を定義するメソッドの名前として使用します。他にも、myvar_=は代入演算子を定義するメソッドの名前として使用します。

リテラル識別子

リテラル識別子はバッククオートで囲まれた任意の文字列を識別子とするものです。`x`, `<client>`, `yield`がそうです。

Scalaの定数

Javaでは定数はすべて大文字のアンダースコア区切りをします。たとえばMAX_VALUEなどです。Scalaでは先頭大文字のキャメルケースを使用します。たとえばMaxValueなどです。

暗黙の型変換

メソッドの多重定義により、r * 2のような演算は定義できましたが、2 * rは実行できません。Int型の2は、Rational型を取る*メソッドが無いからです。しかし、暗黙の型変換を定義することで実現できます。
scala> implicit def intToRational(x: Int) = new Rational(x)
を実行することで2 * rを実行することができます。
暗黙の型変換は21章でさらに解説があるようです。

コップ本をやる 第5章 基本型と演算子

最近Scalaを使うようにしていますが、なんだかうまく使いこなせていない感があります。それはScalaをしっかり理解していないことから来る不安感なので、飛ばし飛ばし読んだコップ本を再度すみからすみまで読み直してしっかり理解しようと考えています。そこで、毎日コップ本を少しずつ進めていこうと思います。このブログには勉強したことを自分用にメモした内容を書いていきます。

第5章 基本型と演算子

基本型

Scalaの基本型はJavaと同じです。intやlongなど互換性のためJavaと同じ型が使えますが、Int、LongとScala用の型を使う方が良いです。それぞれscala.Int、scala.Long型が完全名です。String型だけはJavaでもクラスなのでJavaと同じjava.lang.Stringです。

scalaパッケージとjava.langパッケージは自動的にimportされるのでそれぞれimportの必要はありません。

リテラル

Javaと同じです。

// 16進数
val hex = 0x5
val hex2 = 0x00FF

// 8進数
val oct = 035

// Long
val a = 35L
val b = 35l

ShortとByteは型を指定した上で範囲内の整数値を指定します。
範囲外を指定するとエラーになります。

scala> val a: Short = 32767
a: Short = 32767

scala> val a: Short = 32768
:10: error: type mismatch;
found : Int(32768)
required: Short
val a: Short = 32768

浮動小数点もJavaと同じです。

//小数点を付けるとDouble
scala> val a = 10.0
a: Double = 10.0

// dを付けるとDouble
scala> val a = 10d
a: Double = 10.0

// fを付けるとFloat
scala> val a = 10f
a: Float = 10.0

文字、文字列もJavaと同じです。
scala> val a = ‘b’
a: Char = b

scala> val a = “hoge”
a: String = hoge

Scalaではより便利な文字列リテラルが使えます。
println(“””|hogehoge
|fugafuga”””)
hogehoge
fugafuga

|を先頭に入れることでインデントを入れてもインデントは無視されるようになります。

この章には無いですが、s”i=$i”と文字列リテラルの頭にsを付けることで加工文字列リテラルとする事ができます。(これは便利!)

演算子はメソッド

Scalaの演算子はすべてメソッドです。
1 + 2としても、内部ではInt型1が持つメソッド+に、パラメータとして2を指定して実行するという意味になります。省略せずに書くと1.+(2)ですね。この記法は中置記法と呼び、パラメータを一つ取るメソッドはすべて中置記法が使えます。

// “hoge”.indexOf(‘g’)も次のように中置記法が使えます。
scala> “hoge” indexOf ‘g’
res6: Int = 2

前置演算子

Scalaでは、-10も10にunary_-と言う名前のメソッドを実行すると言う意味になります。前置演算子として定義できるのは-,+,!,~の4つだけです。

後置演算子

引数を取らないメソッドは空括弧を省略でき、またドットを省略することもできます。ドットを省略した場合後置演算子となります。

scala> “HOGE” toLowerCase
res8: String = hoge

Scalaの慣習では副作用を持つメソッドの実行は空括弧を記述し、副作用を持たないメソッドの実行は空括弧を省略します。

オブジェクトの等価性

Javaでは==は同じインスタンスかどうかをチェックしますが、Scalaの==はequals呼び出しとなり、等価性をチェックします。
Javaの==のような同じインスタンスかどうかをScalaでチェックしたい場合は、eqメソッドを使います。

scala> “hoge” eq “hoge”
res18: Boolean = true

scala> “hoge” eq new String(“hoge”)
res17: Boolean = false