こんにちは。前のブログからずいぶん間が空いてしまいました。新しい職場になり、色々大変でした。
その大変さからいろいろ学べた部分もあるのですが、それはまたの機会に書くとして、今回は仕事で使うこととなったScala
と、業務に入る前に読んでいた『実践Scala入門』がどんな感じで役に立ったかについて書いてみます。書籍は以下のリンクからどうぞ。
私は、C#やPythonの経験もありますので、そのあたりと比較してどう感じたか、についても書いていきます。
Scalaとは
Scalable Language
に由来するものだそうです。scalable
とは、「拡張性のある」みたいな意味です。今はJVM上で動作する言語となっています*1。
本書との出会い
転職が決まり、Scalaの勉強が必要そうだと思い手に取ったのが、本書になります。購入自体は昨年で、電子書籍で購入しました。
この時点でのScalaの知識は
という程度でした。つまり素人です。
Scalaの書籍は、通称コップ本と呼ばれる『Scalaスケーラブルプログラミング』という本があります。
この本、難易度が非常に高く、少なくともScala初心者にはハードルが高いです。その中で本書を見つけた自分は運が良かったと思っています。
対象読者
Scalaをこれから始める、という方には、まず本書を読むことをお勧めします。「本書の対象読者」として、
1つ以上のプログラミング言語に慣れていること
という記載がありますので、他の言語からScalaに移ってきた、という方には最適です。
Scalaの学習を書籍から始めるのであれば、現在は本書が最適解かと私は思います。ネット上にはいろいろ情報がありますが、網羅的な知識を得るうえで書籍は良いものです。
私は一応C#とPythonはそこそこできると思っているので、条件に当てはまっています。
書籍感想
書籍の感想です。
Scalaをやったことなかった私にとっては、Scalaとはどういう言語で、どういう部分に力を入れているのか知るきっかけとなりました。
オブジェクト指向畑で育ってきた私にとっては、関数型の考え方自体よくわかっていません。Scalaといえば関数型、という部分ありますが、そこには触れず、一般的なオブジェクト指向言語と同じ部分から解説し、Scalaってこういう便利な機能があるよ!という解説に移ってくれるので、理解しやすいと思いました。
例えば、Scalaのfor
は、最初はループに使う構文として紹介されますが、実際はScalaの強力さを示す構文*2です。その紹介はとりあえず後に置いて、読み進めると分かってくる構成になっています。
もっとも、それでも分かるかと言われたら別問題で。
Scalaのforは、本書を読んで自分なりに理解している気がする状態でソースを書いて、初めて分かってくるような内容です。つまり、本書を読むときは一緒にソースを書くことを強くお勧めします。読んだだけでは本当に理解できません。むしろ混乱します。
ちょっとした例
例として適切かどうか微妙ですが、例えばこういうオブジェクト*3があったとして、
object Request { def hello: Option[String] = { Some("Hello") } def world: Option[String]= { Some("World!") } def none: Option[String] = { None } }
全ての文字列を結合して出力しようとしようとする場合は、以下のように
object Main { def main(args: Array[String]): Unit = { for { o1 <- Request.hello o2 <- Request.world o3 <- Request.none } yield {println(o1 + o2 + o3)} } }
for文で処理が書けます。ちなみに上記は、Request.none
がNone
を返すため、何も出力されません。
上記構文は、「いくつか処理があり、nullを返す可能性がある場合、すべての処理がnullを返さなかった場合のみ処理を実行する」という場合を簡潔に書けるものです。nullチェックがない分、簡潔な処理になります。
ちなみに、o1
等の型はString
となっており、Option[String]
のOption
を自動的に取ってくれます。このあたりが説明しづらいというか、なんというか…<-
という部分でflatMap
が呼ばれているのですが…うん。
こういう感じで、ソースを書かないで説明を聞いても意味が分からなくなるので、ソースを書きながら読みましょう。できればIDEを使いましょう。特にC#等で型付言語に慣れた方は、今どんな型になっているのだろう?とIDEが教えてくれるので、理解の助けになります。
こんな感じで出てきます。(IntelliJ IDEA使用)
リファレンスとして
本書は、Scalaを使っているとよく出てくるOption
、Either
、Future
等について説明が豊富です。
Scalaで書かれたソースを読むようになり、個人的につまづいたのが、
- どこにでも出てくるfor文
- やっぱりどこにでも出てくるmapという関数(亜種含む)
- for文でいつの間にか外れるFuture、Option、Either
というあたりです。Optionについては上で書いた通り、forの中でflatMapが適用されて外れますが、これの意味が本当に分かりませんでした*4。
そういうときに、本書を読み、同じソースを書いて、そしてやっと理解できるという感じで理解が深まります。
なお、Future
はいまだによくわかりません。いつ評価されるんだよ…と思ったら、単に動かしたいときはAwait.result
等を使うと良いそうです。
こういうの用意して、
object FutureTest { def helloAsync: Future[String] = { Future.successful("hello") } }
こうやって呼び出すと、評価されて結果が表示されます。
object Main { def main(args: Array[String]): Unit = { // Futureの戻り値(string)に文字を追加(結果の合成) val f = for { o1 <- FutureTest.helloAsync } yield { o1 + " World!" } // Futureの終了時処理:printlnで出力 f.onComplete { case Success(v) => println(v) case _ => println("failed!") } // 終わるまで待つ Await.result(f, Duration.Inf) } }
末尾のAwait.result
が無いと、何も表示されません。
個人的には、.NETのasync、awaitみたいに書けるほうが好きですが、パターンマッチングを前提にするとScalaの書き方がすっきりしていて良いです。以前の記事ですが、考え方が違うような気がしています。C#の考え方に引きずられて、なかなかピンときていない部分です。
他の言語と比較したScala
あまり触れませんでしたが、Scalaは型推論が非常に強力です。そのため、書いている感覚はPythonに近いものを感じます。リストを用いた処理を三言語で書いてみます。
var l = new List<int>{ 1, 2, 3, 4, 5};
l = list((1, 2, 3, 4, 5))
val l = List(1, 2, 3, 4, 5)
変数をval
で受ける必要はあるものの、右辺は型推論が働いており、List<Int>
として扱われます。new
を書かなくてよいのは、scala.collection.immutble.List
のコンパニオンオブジェクトのapply
で定義されているから、なのですが、本書を読むときっと意味が分かると思います。
なお、Pythonでは
l = list((1, "02", 3, "4", 5))
のように、型が混在していたとしても、実行するまでどうなるか分かりません。一方Scalaだと
l = List(1, "02", 3, "4", 5)
と書くと型がList<Any>
となり、List<Int>
を期待している部分ではコンパイルエラーとなります。
このように、書きやすさと型宣言の安心感を同時に期待できます。
また、case class
という、ざっくりいうと不変オブジェクトを簡単に作り出せる機構がかなり使いやすいです。パターンマッチとの相性が非常によく、DDDでいうところの「値オブジェクト」が持っていると良いとされる性質を持っています。
例として、
case class Person(val name: String, val age: Int);
という宣言を行うと、
val p = Person("aaa", 10) p.age = 2 //compile error
というように、値不変なクラスが生み出せます。また、自動的にequals
をオーバーライドし、
val p1 = Person("aaa", 10) val p2 = Person("aaa", 10) println(p1 == p2) // true
というように、インスタンスの中身が一致していたら同じ、というような比較も行えます。不変であるため、パターンマッチングとの相性も良く、いろいろな箇所で使われています。
業務でのScala
前述の通り、業務でもScalaを使っています。やっぱりプロダクションとして使っているソースは難しく、理解できない部分が多いです*7。
オブジェクト指向であることを全面に押し出したような書き方にしたほうが、全体的な読みやすさは確保しやすいように思いますが、特にtraitの使い方がC#やJavaのInterfaceと全然異なり、そこの理解を深めていくことが、個人的な課題です。
使いこなすともっと読みやすいソースが書けるようになるかも…と思っています。
読み終わってからの学習
本書を読んでいて思ったのは、Scalaすごい!です。ほんとにこの感想で、nullに苦しんでいた人々にOption
という答えを用意し、変更可能なオブジェクトに苦しんでいた人々にcase class
を準備し、優秀な型推論のおかげで型宣言地獄からも解放される。すごい言語だなぁと思いました。
本書を読んで基礎を学んだあとは、とにかくソースを書いて「Scalaらしい」書き方を学んだほうが良いと思います。
他言語から移ってきた人は、どう書いたらよいのか分からず、最初苦しいと思います。私もそうです。とりあえずどういうロジックか分かるけど、Scalaでどのように書くのが良いのか分からない、という期間が続きます。現在進行形で私がそうなのですが。
しかし、考えても分からんものは分からんので、とにかく書いて、可能なら添削を受ける、というのが良いように思います。
Scalaらしい書き方を学ぶ場がほとんどないのが、課題かもしれません。
おわりに
Scalaは難しいです。しかし、本書を読まなければ、もっと苦しんでいたと思います。むしろ、本書が無い状態での日本のScalaって、どういう状況だったのだろうとすら思います。
Scalaは、書きやすさと安全性を両立した言語だと思います。個人的にはJVMかぁ…という気持ちはありますが、環境はIntelliJがあれば整いますし、書いていて気持ちよいというか、楽だなぁと思います。
本書でも触れられますが、関数とメソッドの関係性を明確*8にしていたり、式と文の違いが明確だったり、同じJVMで動くJavaで課題だった部分を解決していたり、高級言語らしい言語だと感じました。
本の内容をなぞるとただの丸写しになってしまうので、内容にそれほど触れない形でのご紹介となりました。Scalaをこれから使う人にとってはもちろん、別の言語を使っているエンジニアにとっても、こういう世界があるのか!という気づきを得る本になると思います。