Scalaらしさを学んで入門のその先に進む


概要

はじめに

2019年4月から新しいチームに配属されることになって、そこではScalaとPHPを使い分けて開発をおこなっていました

4月5月では主に保守運用周り、並びにPHPでの開発を行いつつ、Scalaに慣れるために小さい修正を細々とおこなっていて、6月からついに本格的にScala開発に着手することになりました。

レガシーな技術から新しい技術に乗り換えるということで、その中で開発言語はどうするかというところで、その時に現チームで主流となっているScalaを採用することになりました

それまではPHPでの開発を主に行っていたのでいきなりのコンパイル型言語、しかもJavaも特に習っていない状態での取り組みということで、凄く大きな壁を感じていました

Scalaについて体系的に学べる入門的な本はいくつかありますが、実際にプログラムを書く時に、どのようにプログラムを書いていくのか、どういう時に習ったコードを使うかなどScalaらしさがわからない故に不安を多くいだいていました(今もですが…)

特にScalaは勉強コストが高いと噂されており、本を呼んでいる中で確かにわからない単語が多く、本によっては書いている内容が全く意味がわからなかったりするなど躓きを多く感じていました

Scalaは難しく考えると相当難しく実装することができるのですが、ScalaはJavaとの互換性を第一に考えられた言語なので、「Javaを簡潔に書く」という考え方を元に実装していくことでScalaの難しさに立ち向かわずになんとか実装することができています

この記事ではScalaのプログラムをどういうふうに実装していくか、つまり「Scalaらしさ」について後述する参考資料の内容をベースにしてまとめています
初心者向けではありますが入門書を読んでじゃあプログラムを書きはじめたは良いがどう書いていこう、という人向けにこの記事をまとめることにしています

2ヶ月間しか触っていないのと、Scalaで書かれたOSSを見たりしているわけでもないため、Scala使いの人によっては「この記事は全然間違っている!」などあるかもしれませんがご容赦ください…ご指摘もコメントにてお待ちしております

参考資料

Scalaらしさとは言いつつ、Scalaの書き方については純粋関数型っぽく書こうとする人や、オブジェクト指向的に書こうとする人を始め、様々な意見があるため結論づけることはとても難しいのですが

今回は新卒エンジニア向けのScala研修資料の作者の方が「Scala言語らしさ」を理解しよう! オブジェクト指向と関数型プログラミングの融合とは?という記事を書かれていて、そこに記載されている内容をベースにまとめることとします

ちゃんと読むと長いため、忘備録的に切り取ってまとめている感じです

この方は言語設計者の設計思想を元にScalaらしさについて考えていらっしゃるので説得力も高く今回参考にさせていただいています

設計思想について

Scala作者について

特出事項抜粋

  • 専門の研究分野は型理論と関数型プログラミング専門の研究分野は型理論と関数型プログラミング
    • 他にもオブジェクト指向と関数型の融合というテーマの研究も行っている
  • 現在のScalaに繋がるプログラミング言語を開発している
    • PizzaとFunnel
    • ScalaはGJ(の実用性)とFunnel(のシンプルさ)の中間を目指している

詳細は参考ページで読んでほしいですが、「オブジェクト指向と関数型の融合」「実用性」、「シンプルさ」がキーワードとなっていて、Scalaではそれらの特徴が採用されていそうです

設計思想について

「オブジェクト指向と関数型の融合」というのはオブジェクト指向も、関数型もできる、というハイブリッドなアプローチという意味ではなく、あくまでも融合させる思想となっています

またScalaは現実主義であるため、本来の設計思想に必ずしもそわなくても、それが現実的であれば受け入れられます
例えばScalaは極力valを使って不変にしていくのが推奨されているのですが可変コレクションがあったりするのもそれが影響しています

オブジェクト指向と関数型の融合について

オブジェクト指向プログラミングと関数型プログラミングは対立するものではなく直行するものと考えられ、それぞれは直接関係がないため自由に組み合わせられると考えられています

おさらい:オブジェクト指向とは

  • データと操作の集合をひとまとめにして、インターゲースに定義されたメソッドのみを通じてオブジェクトにアクセスできるようにする
  • 文指向

おさらい:関数型とは

  • 副作用を使わず、式の評価や不変データ構造の変換を通じてプログラムを表現する
  • 式指向

文指向と式指向の違いについて

文指向(手続き型とも言う)

processA()
processB()
processC()

式指向

funcA(
  funcB(
    funcC()))

Scalaの歴史について

  • 歴史を追っていくと実用性を重視したため綺麗でない機能や、Javaとの互換性を考慮したため無理やりな機能追加がしばしばある
  • 現実に対して適度に妥協しつつ進化している言語とも言える

まとめ

考え方

結局どう書いていくのがよいか

  • データと操作はひとまとめにする(オブジェクト指向的アプローチ)
  • データは不変にして、操作は新しいオブジェクトを返すようにする(関数型アプローチ)

という2つを組み合わせてプログラムを書いていくことで自然にオブジェクト指向と関数型は共存できそうです

現実重視

関数型では式指向ではありますが、時に文指向なオブジェクト指向が必要になる時があります、本来ならばそれは妥協と見られますが、Scalaは現実主義な言語として設計されているためいくら本来あるべき姿から妥協しているといっても、現実的な妥協を避けるのは良くはない、という考え方になっています

つまるところ?

Scalaを勉強すると関数型というキーワードが結構出てくるため関数型ってどうやって書くのだろう…と考えてしまいますが、文指向なやりかたに慣れている人が多いかと思うのでまずは基本は文指向(オブジェクト指向的)に書いていって、その際に副作用を取り除くことだけを注意して書いていくようにすれば必然的にScalaらしく書けている…はずです

その後、慣れてきた段階で再度技術書などを読んで理解を深めた上で自分なりの書き方を確立していくのがよいのではないかなと僕個人としては思いました

実践

文指向なコードをScalaらしく書いていく

ここまでの話を前提に実際にプログラムを書いてみます

例として商品リストに消費税をプラスする関数を作成してみました
  def addTax(): List[Double] = {
    var itemList: List[Double] = List(100, 80)
    var tax: Double = 0.08
    var nowTimestamp: Int = 8 // 8月

    // 10月以降は消費税10%にする(日付計算がめんどいなので簡略化させてます)
    if (nowTimestamp >= 10) {
      tax = 0.1
    }
    var itemListAddTax: List[Double] = List()
    for (item <- itemList) {
      itemListAddTax = itemListAddTax :+ item * (tax + 1)
    }
    itemListAddTax
  }
関数の計算結果:List(108.0, 86.4)

この状態でvarはやめてvalにしてみます、こうするだけでもScalaらしさは出ている…はず

  def addTax(): List[Double] = {
    val itemList: List[Double] = List(100, 80)
    val nowTimestamp: Int = 8 // 8月
    val tax: Double = if (nowTimestamp >= 10) {
      0.1
    } else {
      0.08
    }
    itemList.map { item =>
      item * (tax + 1)
    }
  }

アプローチ方法について

  • 10月以降なら消費税を0.1で上書きしている
    • → 条件に応じて返す値を変える
  • 商品リストを税込金額にする
    • → forは使わずmapでリストの中身を変換していく

もう少し関数型に寄せてみます

  def addTax(): List[Double] = {
    itemsAddTax(List(100, 80), taxCalc(8))
  }
  def taxCalc(nowTimestamp: Int): Double = {
    if (nowTimestamp >= 10) {
      0.1
    } else {
      0.08
    }
  }
  def itemsAddTax(itemList: List[Double], tax: Double): List[Double] = {
    itemList.map { item =>
      item * (tax + 1)
    }
  }

このように関数分割していくことで、関数が持つ責務が小さくなるのでわかりやすくなり、参照透過であるためバグを生みにくくしてテストもしやすくなるメリットがあります

しかしコード量は増えたりソースを追いかける時には関数移動で行ったり来たりするのでメリデメはあるため、どの粒度で関数分割をしていくかは個人の裁量によるかと思います

もちろんこれだけのアプローチでやっていけるほどScalaはあまくはないですが、とりあえずvalを使って不変で書いていくことだけでもScalaらしさは多少なりとも出せるかなと思っています


Be First to Comment

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です