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

第二回ChatWork × BizReach合同Scala勉強会を開催しました!

前回は5月に開催させていただいたのですが、あれから数ヶ月、弊社でもScalaのプロジェクトが本格的に立ち上がり、ChatWorkさんにもかとじゅんさんが入社されたということもあり、第二回を開催させていただきました。

f:id:takezoe:20151016102037j:plain

今回はお互いの現状共有のセッションに加えて、予め出したいくつかのお題をかとじゅんさんにレビューしていただくという企画を行いました。その中で様々なScalaのコーディングテクニックを解説していただきました。特にScalaビギナーの方が覚えておくと役に立ちそうなものを紹介したいと思います。

引数をチェックする

Scalaではrequireという関数を使って引数のチェックを行うことができます。引数が条件を満たさない場合はIllegalArgumentExceptionがスローされます。

def hoge(i: Int) = {
  // 引数は0以上であること
  require(i >= 0)
  ...
}

ちなみにこのrequire関数はPredefというオブジェクトで定義されています。ScalaではPredefで定義されているものは暗黙的にインポートされた状態になっており、グローバル関数のように呼び出すことができます。Predefには様々な便利関数が定義されていますが、値のチェックに使えるものとしてはrequire以外にもassertやassumeという関数があります。これらは条件を満たさない場合にAssertionErrorをスローします。

他にPredefで定義されている面白いメソッドとしては???があります。これは以下のように未実装のメソッドに記述しておくためのもので、このメソッドが呼び出されるとNotImplementedErrorがスローされます。

def hoge = ???

コレクションの要素でgroupByする場合はidentityを使う

以下のようなケースはx => xをidentityで置き換えることができます。

// Seqの要素でgroupBy
Seq("a", "b", "c", "a").groupBy(x => x)

// x => xはidentityで置き換えられる
Seq("a", "b", "c", "a").groupBy(identity)

identityはPredefで以下のように定義されており、要するに引数をそのまま返す関数です。groupBy以外にも様々な用途に使うことができるでしょう。

def identity[A](x: A): A = x

Listには末尾に追加するのではなく先頭に追加する

ScalaのListはいわゆるLinkedListなので最後の要素を取得するには先頭から順番に要素を辿っていく必要があります。そのため、Listに要素を追加する場合は末尾ではなく先頭に追加するようにしましょう。

//末尾に要素を追加
val newList = list :+ 4
  
// 先頭に要素を追加
val newList = 4 +: list

追加順のListが必要な場合はすべて追加し終わった後にreverseするといいです。特に再帰の際のアキュムレータとして使う場合など、頻繁に追加を行うケースでパフォーマンス的に大きな問題になってしまう可能性があるので気をつけましょう。

キーと値のタプルのコレクションをtoMapするとMapになる

Scalaではキーと値のタプルを格納したコレクションをtoMapするとMapに変換することができます。

// キーと値のタプルを格納したSeq
val seq: Seq[(String, Int)] = Seq(
  "a" -> 1,
  "b" -> 2,
  "c" -> 3
)

// SeqをMapに変換
val map: Map[String, Int] = seq.toMap

flatMapはfor式でスマートに記述できる

たとえば以下のようにflatMapを使用したコードがあるとします。

val lines = Seq("apple banana", "orange apple mango", "kiwi papaya orange")
val words = lines.flatMap(_.split(" "))
words.map { word =>
  ...
}

上記のコードは以下のように書き換えることができます。この場合だとどっちでも大差ありませんが、flatMapが多段ネストしてしまう場合はfor式を使用した方がスマートに記述することができます。なんでこんなことができるのかはまた別の話…。

for {
  words <- lines
  word  <- words.split(" ")
} yield {
  ...
}

末尾再帰を書く場合終了条件を先頭に書く

ifやパターンマッチに限らず、再帰の終了条件は先頭に書くとわかりやすいとのこと。

def loop1(seq: Seq[String]): Unit = {
  seq match {
    case Nil => () // これをパターンマッチの先頭に書く
    case x :: xs => {
      ...
      loop1(xs)
    }
  }
}

また、これは豆知識なのですが、IntelliJではエディタの左端に末尾再帰になっている場合はf:id:takezoe:20151016102055p:plain、末尾再帰になっていない場合はf:id:takezoe:20151016102105p:plainというアイコンが表示されるそうです。普段IntelliJ使ってますが全然気づきませんでしたw


最後は完全に酔っぱらった加藤さんに非常に中途半端な型クラスの説明をしていただいたり、最近ChatWorkさんにジョインされた@kyo agoさんにSSE(Server Side Event)についてLTをしていただきました。

弊社では業務に追われがちでなかなか定期的なコードレビューができていないのですが、この日のコードレビュー大会は予想以上に盛り上がり、また勉強になる点も多かったので社内でも定期的にみんな集まってのコードレビューができるといいなと思いました。また、かとじゅんさんや吾郷さんにはDDDやScalaの教育についても様々なアドバイスをいただき、大変参考になりました。今後実務にも活かしていきたいと思います。

前回・今回と弊社とChatWorkさんでScala勉強会を行ったのですが、今後はパブリックなScala勉強会を開催してみるのもよいのではと思っています。是非ご期待いただければと思います。