スタディサプリENGLISH のサーバーエンジニアになって0からScala に挑戦してます

bussorenre
27

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2018 の投稿記事です。

この10月より、スタディサプリ ENGLISH のサーバーエンジニアとして Scala を書いている @bussorenre です。突然仕事をやめて農家を始めるかの如く、いつの間にか Scala を書くことになりました。

Scala に触れることになったきっかけ

以前より、関数型と呼ばれる言語には興味関心があったのですが、学生の頃に一度Haskell, Lisp に挫折しており、「関数型言語 = 数学ガチ勢のやべー奴が使うやべーやつ 」 という認識のまま5年くらい経過しました。また、JVM界隈ではKotlinがベターJava 期待の新星という注目を浴びていたこともあって「Scala 大丈夫?」という偏見を持っていました。

持ってましたが、「技術的に新しいチャレンジがしたいな」的な相談を当時の上長にしたところ、「bussorenreくん、型あるほうがイケるっしょ。Scala イケるっしょ」と後押しされて Scala を触ることになりました(注:ものすっっっっっごく会話を端折ってます)。その後、スタディサプリ ENGLISH の サーバーコードを触りながら「コレは素晴らしい!」と感動したので、これまでに学んだ軌跡などを残していきたいと思います。

先に述べると、Scala はベターJava に留めて多くにはもったいないスペックを持った良い言語です。

実際の業務でSaclaを使うにあたっては、スタディサプリ ENGLISH のドメイン/ユースケースあたりを最初に読み解くことでどのような構造で動いているのか把握出来ました。既存のアーキテクチャが綺麗だからこそですね。

Scala の学習

いろいろ遠回りしましたが、以下のページを読みながら学習するのが最短だと感じました。

ドワンゴさんの入社者向けのScala研修テキストです。なんと github.io で公開されており、誰でも読めます。非常に端的に纏まってて良いです。 このページにも書いていますが、下記の書籍と並行して読むのが良いです。

前者は基本的な教科書として、後者は練習問題集として使うのが良いです。コップ本を読んでも理解に苦しむ概念が↑ドワンゴさんの研修テキストで補足されており、さらにそこから得た知識を、実際のプログラミングに落とし込むための手法・練習などが、FP in Scala に纏まっています。特にFP in Scala は、オールドスタイルの手続き言語脳で構成された関数型童貞にはかなり難しく感じます。

var を使うな、val を使え。

Scalaz の学習

スタディサプリ ENGLISH の多くの部分はScalaz に依存しております。Scalazは何かと言うと、Scala をもっと関数型っぽく(Haskellっぽく)かける補完ライブラリです。

こちらも最初は非常に難儀しました。\/ 等の、コップ本の索引には無い記号が出てきて、なんだこれは!?となったり、学習には遠回りしたのですが、こちらを読むことでかなり理解が進みました。

また、Scalazに限らず、Scala 的な考え方とか「よくわからん」となった時に Scala的な考え方 - Scalaがとっつきにくいと思っている人へ を読むことで、理解の助けになりました。非常に良い記事です。

最初はよくわからなかった概念

個人的に、理解するのに1日以上掛かったものの中からいくつか列挙します。

作用と副作用

関数型の世界では戻り値以外の値の操作が発生することは全て副作用です。例えば

def f(x: String): Int = {
    print("Hello" + x)
    return 0
}

というコードが有る時、値を返す0が作用で、画面に表示される print("Hello" + x) は副作用となります。この副作用を如何に減らしていくかが関数型に(というか、モジュール性の高いプログラムを書くのに)必要な肝になります。

モナド

しかし、プログラムには副作用がどうしても必要な場合が多々あります。実際のケースで言うと、DBアクセスやエラー処理、ログ出力等です。副作用を抽象的に関数型的に、楽に扱える仕組みが必要です。そこで登場するのがモナドです。決して某剣のことではありません。

副作用には、例えばDBアクセスだけでなく、例外やログ等も含まれます。

  • 前提となる、関数の出力及び副作用
  • 関数の出力に副作用を付与する仕組み
  • 副作用を考慮せず、関数の出力だけを関数に渡せる仕組み

これら3つの組み合わせをモナドと呼びます。以下のページの説明が最も個人的にわかりやすかったので紹介します。

高階関数

端的にいうと、関数を引数に取る関数の事です。高校数学で言う階差数列とかが高階関数に当たるでしょうか。JavaScript 等によく見られる function を取る関数も高階関数に分類されます。

Scala ではもう高階関数だらけです。FP in Scala の練習問題も、第二章にして高階関数が初登場し、第三章の問題は基本的に高階関数と再帰を用いて解くことになります。慣れていないとゲキムズです。ごほうび 5.0 スタートくらい

部分適用とカリー化

f(x, y):z = (g(x): f(y => z))(y): z = g(x)(y): z

と置換することです。左辺は普通の、2値を取ってz型の値を返す関数です。中辺が少しややこしくて、x を取ってyを引数にz型の値を返す関数を返す関数g(x)が定義出来ます。関数を返す関数にも慣れるしかないです。

で、それらを省略すると右辺の形になります。このような処理をカリー化と言います。

case class

最初apply メソッドがどこから来ているかわからなくて迷走しました。「こんなメソッド定義されてないぞ。どっから来たんだ」ってときは、 case class かどうかを確認し、コップ本のcase class の章を見に行くのが良いかも。

case class は 便利なやつです。要するにクラス定義なんですが、冒頭にcase をつけると便利メソッドを自動で生やしてくれます。

パターンマッチ

C言語やJava などにあるswitch 文の上位互換です。

val list = List(1, true, "J")

for (value <- list) {
    value match {
    case num: Integer => println("num=" + num)
    case bool: Boolean => println("bool=" + bool)
    case name: String if name != "" => println("name=" + name)
    case _ => println("value=" + value)
    }
}

と、このように型の判別、if による細かい条件指定等で、演算内容を分岐させることが出来ます。めっちゃ使います。

Option 型

Swift の時も散々理解に苦しんだOption型です1)Swift ではOptional 型ですが。Option[T] の形で宣言することが出来、T型の値を持つか、そうでなければ None を持ちます。パターンマッチを用いて以下のように利用することが出来ます。

val hoge: Option[Int] = Some(100)
hoge match {
  case Some(x) => x
  case None => 0
}
> res1: Int = 100

map で文脈を維持する

Scala に慣れていない時に見る map はよくわからない物ですが、理解が進むと map 無くしては生きられなくなります。上記の Option 型の例では、パターンマッチを用いて中の値を取り出す(もしくは、値がないときのエラー処理)をしていますが、Option型のまま中の値を操作したいときがわりとよくあります。そういう時に使うのが map です。

val hoge: Option[Int] = Some(100)
hoge map {
  x => x *2
}
> res1: Option[Int] = 200

この例ではOption型の中に値があるのでなんとも思いませんが、以下の例ではどうでしょうか。

val items = List(Some(10), None, Some(100))
items map {
  _.map {
    x => x * 2 
  }
}
res0: List[Option[Int]] = List(Some(20), None, Some(200))

List[Option[Int]] 型を維持しながら、その中の値を操作することに成功しました。これにより、Option.get等で中の値を直接取り出さなくても、中の値を操作することが出来ます。これはものすごく使います。実際はFuture[Option[なんかリクエストの結果]] という形だったりですごく使います。

Option の理解と合わせて ScalaのOptionステキさについてアツく語ってみる が非常にわかりやすかったので紹介します。

まとめ

Scala マンになって三ヶ月ほどしか経過してませんが、未知のパラダイムで実装された言語、及びその言語を用いて開発されたプロダクトコードにチャレンジするというのは、非常に良い経験になってます。最初の二ヶ月近くを、インプットに100%時間を割いても良いという、上長の寛大なマネージメントにより、後々の業務が楽になってます。

関数型プログラミングというパラダイムが自分の中にインストールされつつあることで、副作用的にSwift への理解が深まってます。手続き型チックに書いてたコードが、関数型チックに書けるべき場面が見えてきたりと非常に良いです。

最後に重要なことですが、Scala 書くのはめちゃくちゃ楽しいです。CでOSのメモリ処理を書いてた時と同じような、しかしベクトルの違う楽しさを感じてます。

来年もやっていくぞい 💪

脚注   [ + ]

1. Swift ではOptional 型ですが。