おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

社内Scala勉強会その2

そういえば4月に退職してから何をしてたか全然書いてなかったですが、半年ほど自宅警備員を続けた後にフリーランスのエンジニアとして活動を始めています。今はとある会社に常駐しつつ Rails 3.1.1 を使って開発をしています。

ところで2週間ほど前から、ふとしたきっかけで社内のエンジニアの方々と週一ペースの Scala 勉強会を始めることになりました。教材は twitter が公開しているScala Schoolです。僕が先週やった分のまとめ担当になったので、まとめを書いていこうと思います!先週は Scala School - Basics continued を一通りみんなで眺めました。

バックナンバー

初回の勉強会の内容を @satococoa さんにまとめていただきました。

社内Scala勉強会始めました - 223 Software

apply methods

オブジェクトをメソッド的に使えるシンタックスシュガーを提供してくれる。後述するような、 Companion Object をファクトリとして使う時に利用するのが主な使われ方?

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

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

scala> bar()
res8: Int = 0

Objects

定義したクラスの一つのインスタンスを保持するものらしい。singleton instance のようなもの?

object Timer {
  var count = 0

  def currentCount(): Long = {
    count += 1
    count
  }
}

実行するたびにインクリメントされたcountが返る。

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

scala> Timer.currentCount()
res1: Long = 2

scala> Timer.currentCount()
res2: Long = 3

クラスとオブジェクトは同じ名前を持てて、その場合 "Companion Object" と呼ばれる。Companion Object は主に下記のような感じで定義されることが多いらしい。

class Bar(foo: String)

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

こうすると、Bar() で Barクラスのインスタンスが返る!べんり。

Functions are Objects

関数は trait らしい。例えば引数を一つ取る関数は "Function1" という名前の trait を mixin したインスタンス。"Function1" は apply() を定義していて、オブジェクトを関数っぽく呼べる。

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

scala> addOne(1)
res2: Int = 2

上記のコードは

def addOne(m: Int): Int = m + 1

と等価っていう認識で良いんだろうか。それとも「def キーワード使わなくても同じ事が出来るよ」ってこと?→コップ本読んだけど等価って言う認識でよさそう。

extends Function1[Int, Int] は extends (Int => Int) に省略可能らしいです。でもそもそも[Int, Int]が何なのか説明がないのでよくわかりません…Int を引数にとって Int を返すってことかな。

Packages

package は java っぽい感じ。object を使うと static function っぽく書ける。

package com.twitter.example

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

上記のように定義すると、パッケージの外から下記のように呼び出せる。

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

Pattern Matching

Ruby で言う case when ですね。どうもcase whenとは違うらしい…!!今度調べます。

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

scala はパターンマッチング中に if を使うことで、複雑な条件をスマートに分岐できるっぽい。

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

パターンマッチング用の変数は自動で "i" になるみたい。上記のコードでは "i" を使っていますが、変数名に特に縛りは無いみたいです。x とか別の変数名にしても普通に動作しました。

上記の例だと if を使うメリットは特にないけど、もっと複雑な条件で恩恵を受けられそうな文法。

Matching on class members

Calculator というクラスのメンバを調べて場合分けするのを普通に書くと下記のようになる。

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"
}

後述する case class というものを使うともっとスマートに書けるらしい。

case classes

case class で宣言して、メソッド呼び出しのような形で case class のインスタンスを作る。

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

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

case class は自動で同値判定用のメソッドと toString メソッドを持ってる。あと他の普通のクラスと同じようにメソッドを持たせることもできる。

case classes with pattern matching

case class を使ってパターンマッチングをしてみると下記のようになる。確かに見やすくなってる!

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 の一番下は下記のようにも書ける

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

Calculatorクラスのインスタンス以外もマッチングさせたかったら下記のように書ける

case _ => "Calculator of unknown type"

マッチした値に別の名前を付けて使うことも出来る。下記はマッチした Caluculator インスタンスに c という名前を付けて、戻り値となる文字列中で使ってる。

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

Exceptions

例外の書き方はjavaと同じ。

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()
}

javaと違うのは、式として使うことも出来るということ。下記のような書き方はRubyでも時々使いますね。

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()
}

雑感

Scala Schoolを使って勉強会を2回やってみて、この教材はおそらくScalaに詳しい人が近くに居る想定で作られているんだろうなと感じました。少なくともscalaを知らない初心者が一人でやるものじゃなさそう。勉強会ドリブンじゃなくて一人だったら続かなかっただろうなー。

なんとか最後までやりきって、簡単なwebアプリを作って公開できるくらい Scala をマスターできたらいいなと思います。