市ヶ谷Geek★Night #3「Scalaはじめました」
オプトが主催する「市ヶ谷Geek★Night」。第三弾となる今回のテーマは「Scalaはじめました」。
▲株式会社オプト 執行役員 石原靖士さん
Scalaを採用し、技術的なチャレンジをしている企業が登壇し、秘伝のScalaレシピを紹介した。特にJavaエンジニアがScala取り組む際の心得や、未経験からプロダクトコードを扱えるようになるまでの取り組みなど、「これからScalaを学んでいきたい」という未経験者や初心者に向けた勉強会となった。
- Scalaと過ごした5ヶ月間(Demand Side Science岡田遥来さん)
- Javaエンジニアチームが始めやすいScalaコーディングスタイル(エムスリー瀬良和弘さん)
- ループで遊ぼう(Tech to Value中村学さん)
Scala未経験者がScalaを習得するコツとは
最初に登壇したのは、Demand Side Science(DSS)の岡田遥来さん。セッションタイトルは「Scalaと過ごした5ヶ月間」。
▲Demand Side Science 株式会社 岡田遥来さん
岡田さんは2015年3月に同社に転職し、Scalaエンジニアへと転身した。DSSは12年11月に設立されたアドテクの会社。社員10人で3PASやアドベリフィーケーションなどのアドテク関連のプロダクトを開発している。今はScalaエンジニアとして業務に従事している岡田さんだが、前職ではC#やRailsなどを使っており、ScalaはDSSに入るまでは未経験だった。
「シンタックスシュガーが多く、既存コードを読めるまでに苦労した」と岡田さんは振り返る。例えばScala製HTTPライブラリのDispatchのサンプルコードでは、スペース区切りで3つキーワードが並んでおり、どう結合しているのか、どういうメソッドを呼び出しているのかがわかりづらく、もやっとしたという。
Scalaでは引数が1つのメソッドは、ドットと括弧なしで呼びだせる(中置記法が可能である)。サンプルの場合は、implicit conversion というScalaの機能を利用して簡潔な記述になっているが、これにより実際にどんな処理が行われるかはわかりにくい部分がある。
「このようなことから、とっつきにくいと感じた」と岡田さん。そこで、次の資料で勉強したという。
まずは「Scala School!」。内容がコンパクトにまとまっていて、最低限のScalaの言語仕様を一通り押さえられる。次の登壇者である瀬良さんによる日本語訳もあり、お勧めなのだそうだ。
第二に「Ninety-Nine Scala Problems」。こちらもWeb資料で、再帰の練習によいという。
続いてもっとScalaと仲良くなるためにに有効だったことについても紹介。
ひとつは、社内のチャットボット(amemiyasan)に導入されているScalaのREPLを活用したこと。ちなみにREPLとは対話的にコードを実行する環境のこと。「これのうまい書き方がある」みたいな議論が盛んになり、「社内のみんなともコードで会話するので、Scalaの勉強が進んだ」と岡田さんは説明する。
もうひとつは、amemiyasanはベンチマークを取る機能もあるので、それを利用して社内でベンチマークを競ったりしたこと。「これにより特にコレクションの理解が進んだ。また推測するな、計測せよの文化が根付いた」と、実際にamemiyasanのデモで説明した。
最後に「Scalaは仲良くなると面白い。仲良くなるためにも、みんなでREPLを活用することをお勧めしたい」と語り、セッションを締めた。
Javaが得意なScala初心者が馴染みやすいScalaのコードスタイル
続いて登壇したのは、エムスリーの瀬良和弘さん。セッションタイトルは「Javaエンジニアチームが始めやすいScalaコーディングスタイル」。
瀬良さんは2009年にScalaを初めて触り、現在は「ScalikeJDBC」プロジェクトリードや「Skinny Framework」のプロジェクトリードを務めているほか、Scalatra、Json4s、Scalateメンテナとしても活動している。
瀬良さんはJavaを得意としているScala初学者のプログラマを想定し、Javaエンジニアが馴染みやすいというそのスタイルについて7つのトピックに分けて具体的に紹介した。
▲エムスリー株式会社 瀬良和弘さん
1.重厚な設計を忘れてシンプルに書き始める
Scalaに馴染むにはどうすればよいのか。「個人的な意見だが、JavaプログラマはあえてScalaを、静的型のRubyと捉えて、バランス感覚を養うとよい」と瀬良さん。
さらに、「JavaプログラマはあえてScalaを静的型のRubyと捉えてバランス感覚を養うとよい」「case classはまずはvalue objectのような入れ物として慣れるとよい」「単純な結果のキャッシュには、メソッド定義の代わりにlazy valを使うだけでよい」「ScalaではDIコンテナがなくてテスタビリティを確保できる」と語り、DIなしでどのようにテスタビリティを確保するかについて、以下のような簡単なサンプルコードを紹介した。
2.Javaプログラマらしい整然としたプロジェクト
Scalaプログラマの中には一つのソースファイルにいろいろなクラスを詰め込んでしまう人がいるとのこと。
「こうするととっちらかってしまうので、Sealedを使うとき以外は、1ファイル1モジュールでファイル名とモジュール名を一致させるようにしている」と瀬良さんは語る。
また「publicなメンバ・メソッドには型を明示した方が良い」とし、その理由としてコードのメンテナンス性の向上とコンパイル時間の短縮を挙げた。
型の明示を指定する際、IntelliJ IDEAの補完機能が非常に便利とのことで、以下のようなスクリーンショットを紹介しながら参加者に利用を勧めた。
次に、暗黙の型変換による既存クラスの拡張は乱用すべきでないとし、特にデフォルトで有効になるような使い方には慎重になるべきであると述べた。
また Java でよく見られる「とりあえず interface を定義」というスタイルも有効だが、中途半端にやらないように注意したいと語った。
3.文ではなく式であることを意識してif/elseを使う
再代入不可でvarを書かない制約はさほど厳しくないが、メソッド・コードブロックが返すべき型を明示すること、ローカル変数でもコードブロックをうまく使うこと、返すべき型の値を返すif/else式やパターンマッチにするとよいとのこと。
またtry/catchでも返せる特性を活かす、Option(..).map(..).getOrElse(..)のようなイディオムを用いる、メソッドをチェインしすぎない、適度に名前を付ける、などのノウハウを紹介した。
4.例外を否定するかどうかはスタンスを取る
Scalaは例外をサポートする言語だが、Javaのような検査例外の仕組みがないだけでなく、標準APIにTry、Either、Futureのようにthrowされた例外を保持するデータ型が存在している。
例外をthrowするのか、例外を保持するデータ型を用いるのかは統一を徹底的にやるべきであると語った。また、必ずしも標準のデータ型を用いる必要はなく、成功・失敗を反映したオブジェクトを返してもよいとした。
5.Futureはスレッドプールを強く意識する
用途ごとにスレッドプールを分ける方が良い場合は自前のExecutionContextを複数用意して使い分ける必要があり、さらにスレッドプールを浪費する処理はないか、プールの設定は用途に対して妥当かも注意深く設計すべきであると語った。
また、非同期実行でないFutureもあるので、その場合はただ型を合わせるだけでなく、その挙動を理解し想像することが大事であるとした。
6.Javaのモジュールの知識を活かす
「目的を達成するために、Javaの既存コードを組み合わせることは何ら問題ない」と瀬良さんは言う。既存のコードの流用だけでなく、JDBC等の標準規格の利用、AWS等主要なサービスが提供する標準Java SDKを利用できるといったメリットもある。
Java のモジュールに依存した箇所はどうしても Java っぽいコードになってしまうが、メソッドの中に閉じ込めるなど局所的に使うとよいとのこと。
それでも、戻り値としてはJavaの型が返ってしまうが、これを完全にラップしようとするとそれなりにコストがかかるので、業務で扱う場合はほどほどにしておくことを勧めた。
また部分的にJavaを使うことで得られるメリットとして、その部分についてはScala本体のバイナリ互換の問題から開放されるという点を挙げた。
7.チームが目指したいScalaプロジェクトを定める
仕事でScalaを使う場合はチームメンバーがハッピーになることが大事。つまりチームのためのScalaコードであるべきとの考えを述べた。
客観的な目線からScalaプロジェクトを捉え、「Scalaにしてよかったよね、と認識される状況を作ることが大事だ」と瀬良さんは言う。
「Javaプログラマ的美徳はScalaでも生きる。とにかくScalaは凝りすぎずシンプルに書くこと。そして基本returnを書かずに全ては式にすること。例外を保持するデータ型はその設計になれてから取り組むこと。そしてチームのためのScalaコードを書いてほしい」最後にこうまとめ、セッションを終えた。
▲勉強会では、オプトさんから今半のすき焼き弁当の差し入れもあった
Monoidを駆使し、ループをなくす
3番目に登壇したのはTech to Valueの中村学さん。セッションタイトルは「ループで遊ぼう」。中村さんはJapan Scala Associationの理事でもあり、来年1月に「Scala Matsuri2016」を開催予定している。また、隔週で開催されている「rpscala」という勉強会のメンバーでもある。
「今日のセッションではループの書き換えを通してScalaの表現力、および関数型的な手法のメリットを紹介する」と語り、セッションをスタートさせた。
▲株式会社Tech to Value 中村学さん
まず次のようなコードを披露した。これは「高階関数を使うと次のように書き直せる」と中村さん。
アイテムを取りたい場合はcreateItemをmapし、コードに関してはflatMapすれば、リストが全部展開されるので、allItemsとallCodesコードが取得できるようになる。
こうするとmutableがなくなり、シンプルになったが、全件ループが2回起きている。
ではこれをなくすにはどうすればよいか。そこで導入するのが「Monoidだ」と中村さんは説明する。
Monoidとは、ある集合と2項演算の組み合わせのこと。2項演算と集合についてはいくつか条件がある。第一の条件は演算結果も集合の要素であること(演算に対し閉じている)。
次に演算が結合法則を満たしていること。例えばa、b、cを集合の要素として、演算を|+| とした時、(a |+| b) |+| c == a |+| (b |+| c) が成り立つということである。「注意してほしいのは交換法則ではないことだ」と指摘する。
第三に単位元が存在すること。
- e |+| a == a
- a == a |+| e
というようにすべての要素に対して上記が成り立つeが存在することだ。
これは次の具体例を見ると分かりやすい。
整数と足し算
- 整数 + 整数 = 整数 (演算に対し閉じている)
- (1 + 2) + 3 == 1 + (2 + 3) (結合法則を満たす)
- 0 + a == a かつ a + 0 == a (単位元 0 が存在する)
Listと連結
- List[Int] ++ List[Int] の結果は List[Int]
- (List(1) ++ List(2)) ++ List(3) ==
List(1) ++ (List(2) ++ List(3)) - Nil1 ++ List(1) == List(1) かつ List(2) ++ Nil1 == List(2)
Monoidではない例としては整数と割り算の組み合わせである。整数÷整数の結果は整数とは限らないからだ。また整数と引き算も結合法則を満たさないため、Monoidではない。
これをコードにすると次のようになる。
Monoidをどう使えばよいのか。「Monoidの定義はこうやってする」と中村さんは説明を続ける。もしListの要素がMonoidであれば、畳み込み処理が可能だ。
第一引数に単位元を入れて、第二引数に二項演算を入れる。そしてその計算結果を次の初期値として考えるのである。
もちろん常に要素がMonoidとは限らない。その場合は、foldMapを定義する。第一引数にリスト、第二引数にMonoid[B]を受け取るのである。次の引数としてfからBに変換する関数を受け取る。そうすると変換しながら叩き込み処理ができるようになるというわけだ。
flatMapがmapしてflattenするイメージなら、foldMapはmapしてfoldするイメージ。このfoldMapを使うと、ある型のMonoidインスタンスと、その型への変換処理を渡すだけで簡単に畳み込みができる。
とはいえ、毎回、Monoid値を渡すのは面倒くさい。そこで導入するのがimplicit引数である。最後の引数グループにimplicitを付けると、そのグループのすべての引数はimplicit parameterになる。
implicit parameterは、スコープ中に同じ型のimplicitな値があればコンパイル時に自動的に挿入される。スコープ中にimplicitな値がなければコンパイルエラーとなる。こうして改善したfoldMapを利用すると、最初のコードは以下のように書き直せる。
書き直したコードも依然として2重ループのままだが、「この2重ループはMonoidの合成で解消できる」と中村さん。だがMonoidの合成で解消できる。2つのMonoid[A]とMonoid[B]を元に、タプルのモノイド、Monoid[(A,B)]を定義することができるからだ。
タプルとはリストとは異なり、複数の型の組み合わせを表現できる型。このタプルのMonoidを使うことで、先のコードを1回ループのコードに書き換えることができる。
Scalaでは変数の宣言時にパターンマッチが使用できる。foldMapの結果が (List[Item],List[Code]) を返すのでallItemsにList[Item]、allCodesにList[Code] がそれぞれ代入されるのである。
「なぜ普通のforではダメなのか」。それについても中村さんは回答する。最初のコードをテストしようと思ったら、ループ全体をテストする必要があるからだ。もちろんループの中味を1件取り出してリストバッファーを受け取るように括り出してテストを書くことは可能だが、コードは追いづらく書きづらいものになる。
一方Monoidを使ったループでは、1件のProductをタプルに変換するテストとループ処理全体のテストとを分けてテストができる。
また前後の状態に依存することなく結合則が保証されているので、リストを細かく分割するなどして、並列処理することも可能になる。むしろforで書くよりもメリットが得られるのだ。
「当社ではScalaオンラインコードレビューというサービスを提供している。チームメンバーにScalaのスペシャリストがいない。もっと業務コードに密接に絡んだ質問がしたい方は、ぜひ、利用を検討してほしい」と、中村さんはセッションを締めた。
あなたにとって「理想の会社」とは?
「理想の会社で働きたい!」――― いたって単純で、誰もがそう思うこと。
では、あなたにとって理想の会社とは?
あなたが思い描いた「理想」は、きっと今あなたが抱いている「不満」の裏返し。
- 残業が多い、服装が自由じゃない、エンジニア向きの設備がない、
- BtoBの開発だけだと反応がなくてつまらない、
- 会社全体が新しい技術や開発スタイルに関心がない、
- ずっとコードを書いていきたいのに管理職しか道がない…
あなたが抱くその「不満」を「理想」に変えられる会社がどこかにあるはずです。
ITエンジニアのための求人サイト「CodeIQ JOBS」は、スキルやこだわりで検索ができます。
また開発環境や技術への取り組みなど、ITエンジニアがスキルを発揮するために必要な情報を多く掲載しています。
さらに、「CodeIQ」で得た評価をもとに、掲載されている企業の求人へ応募することも可能。