SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

『実践Scala入門』を読んだ(そして仕事でScalaを使った)

こんにちは。前のブログからずいぶん間が空いてしまいました。新しい職場になり、色々大変でした。

その大変さからいろいろ学べた部分もあるのですが、それはまたの機会に書くとして、今回は仕事で使うこととなったScalaと、業務に入る前に読んでいた『実践Scala入門』がどんな感じで役に立ったかについて書いてみます。書籍は以下のリンクからどうぞ。

gihyo.jp

私は、C#Pythonの経験もありますので、そのあたりと比較してどう感じたか、についても書いていきます。

Scalaとは

Scalaという名前は、Wikipediaにもある通り、

Scalable Language

に由来するものだそうです。scalableとは、「拡張性のある」みたいな意味です。今はJVM上で動作する言語となっています*1

本書との出会い

転職が決まり、Scalaの勉強が必要そうだと思い手に取ったのが、本書になります。購入自体は昨年で、電子書籍で購入しました。

f:id:hiroronn:20190429132051p:plain

この時点でのScalaの知識は

という程度でした。つまり素人です。

Scalaの書籍は、通称コップ本と呼ばれる『Scalaスケーラブルプログラミング』という本があります。

honto.jp

この本、難易度が非常に高く、少なくともScala初心者にはハードルが高いです。その中で本書を見つけた自分は運が良かったと思っています。

対象読者

Scalaをこれから始める、という方には、まず本書を読むことをお勧めします。「本書の対象読者」として、

1つ以上のプログラミング言語に慣れていること

という記載がありますので、他の言語からScalaに移ってきた、という方には最適です。

Scalaの学習を書籍から始めるのであれば、現在は本書が最適解かと私は思います。ネット上にはいろいろ情報がありますが、網羅的な知識を得るうえで書籍は良いものです。

私は一応C#Pythonはそこそこできると思っているので、条件に当てはまっています。

書籍感想

書籍の感想です。

Scalaをやったことなかった私にとっては、Scalaとはどういう言語で、どういう部分に力を入れているのか知るきっかけとなりました。

オブジェクト指向畑で育ってきた私にとっては、関数型の考え方自体よくわかっていません。Scalaといえば関数型、という部分ありますが、そこには触れず、一般的なオブジェクト指向言語と同じ部分から解説し、Scalaってこういう便利な機能があるよ!という解説に移ってくれるので、理解しやすいと思いました。

例えば、Scalaforは、最初はループに使う構文として紹介されますが、実際は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.noneNoneを返すため、何も出力されません。

上記構文は、「いくつか処理があり、nullを返す可能性がある場合、すべての処理がnullを返さなかった場合のみ処理を実行する」という場合を簡潔に書けるものです。nullチェックがない分、簡潔な処理になります。

ちなみに、o1等の型はStringとなっており、Option[String]Optionを自動的に取ってくれます。このあたりが説明しづらいというか、なんというか…<-という部分でflatMapが呼ばれているのですが…うん。

こういう感じで、ソースを書かないで説明を聞いても意味が分からなくなるので、ソースを書きながら読みましょう。できればIDEを使いましょう。特にC#等で型付言語に慣れた方は、今どんな型になっているのだろう?とIDEが教えてくれるので、理解の助けになります。

こんな感じで出てきます。(IntelliJ IDEA使用)

f:id:hiroronn:20190429151202p:plain

リファレンスとして

本書は、Scalaを使っているとよく出てくるOptionEitherFuture等について説明が豊富です。

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#の考え方に引きずられて、なかなかピンときていない部分です。

hiroronn.hatenablog.jp

他の言語と比較した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をこれから使う人にとってはもちろん、別の言語を使っているエンジニアにとっても、こういう世界があるのか!という気づきを得る本になると思います。

honto.jp

*1:.NETのサポートがあったらしいが、打ち切られているとのこと

*2:本書中、強力な構文だけど、とりあえずループではこう使うという解説のみ行っている

*3:C#Javaでいうところのstaticクラスに近いもの

*4:プロダクトのソースだと、この辺りは当然分かっている前提になっているし、自分でなんとなく書くとコンパイルエラーになる事象が多発するので、ある程度知識が無いとわけが分からない

*5:Enumerable.Range使えば型宣言不要

*6:引数がIterableなので、一度Tupleにして渡している

*7:型パズルと揶揄される

*8:関数はただのtrait。なので引数で渡す等が簡単に行える。C#のFuncをより扱いやすくしている感じ