読者です 読者をやめる 読者になる 読者になる

seratch's weblog in Japanese

About Scala, Java and Ruby programming in Japaense. If you need English information, go to http://blog.seratch.net/

Scala School意訳(Basics continued)

Scala Scala School

http://twitter.github.com/scala_school/basics2.html

以下は私の方でtypoや表示崩れを直したものです。

https://github.com/seratch/scala_school/blob/master/web/_posts/2011-05-02-lesson.textile

誤訳などありましたら、お手数ですが、ご指摘いただければ幸いです。

apply methods

applyメソッドはクラス、オブジェクトの主用途が一つなときにこのような便利な糖衣構文を提供します。

class Foo
object FooMaker {
  def apply() = new Foo
}

scala> class Bar {
     |   def apply() = 0
     | }
defined class Bar

scala> val bar = new Bar
bar: Bar = Bar@47711479

scala> bar()
res8: Int = 0

このインスタンス(=bar)はまるでメソッド呼び出しのように見えます。
この話題についてはまたあとで触れます!

(訳者追記)
ご覧の通り、applyメソッドは「()」として呼び出せる特殊なメソッドです。

FooMakerの例は定義されたまま利用例の提示がないので念のため補足です。

val foo: Foo = FooMaker()
val foo: Foo = FooMaker.apply()
val foo: Foo = FooMaker.apply

Objects

オブジェクトはあるクラスのシングルトンなインスタンスを保持するために利用します。
また、ファクトリーとしてもよく利用されます。

object Timer {
  val count = 0
  def currentCount(): Long = {
    count += 1
    count
  }
}

このように使います。

scala> Timer.currentCount()
res0: Long = 1

クラスとオブジェクトは同じ名前を持つことができます。
このようなオブジェクトをコンパニオンオブジェクトと呼びます。
コンパニオンオブジェクトは一般的にファクトリーとして使われます。

以下はインスタンス生成時にnewを使う必要がないようにするだけのささいなサンプル例です。

class Bar(foo: String)

object Bar {
  def apply(foo: String) = new Bar(foo)
}

Functions are Objects

Scalaでは、よくobject-functional programmingが話題に上ります。
これは何を意味しているのでしょう?実際のところ、Function(型)とは何でしょうか?

Functionはtraitの組み合わせです。
より厳密にいえば、一つの引数を受け取る関数はFunction1というtraitのインスタンスです。
このtraitは(前節で出てきた)applyメソッドによる糖衣構文によって、まるで関数のようにオブジェクトを呼び出す事を可能にしています。

cala> object addOne extends Function1[Int, Int] {
     |   def apply(m: Int): Int = m + 1
     | }
defined module AddOne

scala> addOne(1)
res2: Int = 2

Function1は全部で22まであります。
なぜ22なのでしょう?それは無作為なマジックナンバーに過ぎません。
私はこれまで22個以上の引数を持つ関数が必要になったことはないので、これはうまくいっているようです。

applyによる糖衣構文はオブジェクトと関数プログラミングの二重性を一つにまとめることに役立っています。
クラスをやり取りし、それを関数として利用することができます、関数とは隠されたクラスのインスタンスに過ぎません。

では、それはクラスにメソッドを定義する度に、Function*型のインスタンスを取得しているということになるのでしょうか?
いいえ、そうではありません。クラスにあるメソッドはあくまでもメソッドです。
REPLの中で単独に定義したメソッドはFunction*型のインスタンスになります。

クラスはFunctionを継承することもでき、そのインスタンスは()で呼び出す事ができます。

scala> class AddOne extends Function1[Int, Int] {
     |   def apply(m: Int): Int = m + 1
     | }
defined class AddOne

scala> val plusOne = new AddOne()
plusOne: AddOne = <function1>

scala> plusOne(1)
res0: Int = 2extends Function1[Int, Int] 」のもっと便利な省略記法は「extends (Int => Int)」です。

class AddOne extends (Int => Int) {
  def apply(m: Int): Int = m + 1
}

Packages

パッケージの中にコードを整理する事ができます。

package com.twitter.example

ファイルの先頭に定義されたパッケージは、そのファイルに書かれた全てがそのパッケージに属する事を宣言します。

値や関数をclassやobjectの外に書くことはできません。オブジェクトはstaticな関数をまとめるのにとても便利な道具です。

package com.twitter.example

object colorHolder {
  val BLUE = "Blue"
  val RED = "Red"
}

メンバーに直接アクセスすることができます。

println("the color is: " + com.twitter.example.colorHolder.BLUE)

オブジェクトを定義したとき、ScalaのREPLは何と出力するか気づいていましたか。

scala> object colorHolder {
     |   val Blue = "Blue"
     |   val Red = "Red"
     | }
defined module colorHolder

これはScalaの設計者がオブジェクトをモジュールシステムの不可欠な要素として設計したということを示す、小さなヒントです。

Pattern Matching

Scalaの最も便利なものの一つです。

値に対してマッチさせる例です。

val times = 1

times match {
  case 1 => "one"
  case 2 => "two"
  case _ => "some other number"
}

ガードを使ってマッチさせる例です。

times match {
  case i if i == 1 => "one"
  case i if i == 2 => "two"
  case _ => "some other number"
}

どのように変数iに値が捕捉されているかに注目してください。
最後のcase文に出てきた「_」はワイルドカードです。
これは全てのパターンを扱う事を保証します。
そうでなければ、マッチしない値を渡されたときにランタイムエラーに苦しむことになるでしょう。
これについては後ほど取り上げます。

ガードについてはこちらを。

ガード (プログラミング) - Wikipedia

Matching on class members

前に出てきた計算機の例を思い出してください。
計算機をそのタイプによって分類してみましょう。

def calcType(calc: Calculator) = calc match {
  case calc.brand == "hp" && calc.model == "20B" => "financial"
  case calc.brand == "hp" && calc.model == "48G" => "scientific"
  case calc.brand == "hp" && calc.model == "30B" => "business"
  case _ => "unknown"
}

わお!これはつらいですね。ありがたい事にScalaはこういった問題のために便利な道具を提供しています。

Case Classes

ケースクラスはクラスの中身を保持したりマッチさせたりする事がとても便利にできます。
また、newキーワードなしにインスタンス化することができます。

scala> case class Calculator(brand: String, model: String)
defined class Calculator

scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)

ケースクラスはコンストラクタ引数に基づいて、便利な等式やtoStringメソッドを自動的に生成します。

scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)

scala> val hp20B = Calculator("hp", "20b")
hp20B: Calculator = Calculator(hp,20b)

scala> hp20b == hp20B
res6: Boolean = true

ケースクラスは普通のクラスのようにメソッドを定義することができます。

ケースクラスを定義すると、コンパニオンオブジェクト(同名のobject)が自動生成されます。
このコンパニオンオブジェクトにapplyメソッドが定義されているのでnewなしでインスタンス化することができます。

CASE CLASSES WITH PATTERN MATCTING
ケースクラスはパターンマッチで利用するために設計されています。
では、先ほどの計算機の分類器をシンプルにしてみましょう。

val hp20b = Calculator("hp", "20B")
val hp30b = Calculator("hp", "30B")

def calcType(calc: Calculator) = calc match {
  case Calculator("hp", "20B") => "financial"
  case Calculator("hp", "48G") => "scientific"
  case Calculator("hp", "30B") => "business"
  case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}

最後のマッチ(= case Calculator(ourBrand, ourModel))の代替としては

 case Calculator(_, _) => "Calculator of unknown type"

もしくはシンプルにCalculator型ということすら全く指定しないというやり方もあります。

 case _ => "Calculator of unknown type"

あるいは、マッチした値を違う名前に束縛することもできます。

  case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)

ケースクラスにはunapplyという特殊なメソッドが自動生成されていて、これの働きによって「case Calculator("hp", "20B")」のような記述が可能になっています。

http://d.hatena.ne.jp/seratch2/20110429/1304090707

Exceptions

Scalaにはパターンマッチを使ったtry-catch-finally構文の例外処理があります。

try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
} finally {
  remoteCalculatorService.close()
}

tryから始まる部分も(if/elseなどと同様に)式指向です。

val result: Int = try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => {
    log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
    0
  }
} finally {
  remoteCalculatorService.close()
}

これはあくまでtry-catch-finallyがScalaの(ほとんど)全てのものと同様に式として結果を返す事を示す例であり、優れたプログラミングスタイルの例ではありません。
なお、finallyは例外がハンドルされた後に呼び出されるため、式の一部ではありません。