Magnolia Tech

いつもコードのことばかり考えている人のために。

『Scala関数型デザイン&プログラミング』の演習問題をScala3で解く その5

blog.magnolia.tech

タイトルで言うほどScala3関係無いですね。単に読書メモだと思ってください。

例外を使わないエラー処理

ソフトウェアの複雑度を示すメトリクスに「循環的複雑度」というのがあります。

循環的複雑度 - Wikipedia

簡単に言うと条件分岐やループが有ると数値が上がっていって、一定の数値を超えるとコードを把握することが困難となり、バグが混入する確率が上がっていく、という考え方です。

この計測方法が適切か?閾値は適切か?というのは組織文化に依るものなので、パラメータの調整をしながら適用していくのが適切ですが、おおまかな考え方については非常に同意できる考え方ですね。

初めてOption型を知った時に、この「循環的複雑度」が下がる仕組みだな、と感じました。Option型にmapなどを適用するとき、Someならば処理が先に進み、Noneならば何も行われません。foldメソッドを使えばそれぞれに対する処理が、if文なしにかけます。ただ、本質的には分岐は存在するのでテストはしないといけないので、イデオムで書かれることにより「読み解く脳の負荷が下がる」というメリットは有っても本質的な複雑度は変わらない(理解のしやすさは違う)というところがポイントでしょう。

下限境界

本文でも触れられていますが、orElseメソッドの型シグニチャはちょっと複雑な指定がされています。これは、下限境界と呼ばれる指定です。

def orElse[B>:A](ob: => Option[B]): Option[B]

コレクション系のライブラリを自分で書く時に気をつけないといけない点ですね。公式ドキュメントに詳しく解説があります。

docs.scala-lang.org

map2, sequence, travesableについて

本書の解説や、演習問題にはScalaの標準ライブラリには無い関数がいくつか出てきます。例えば、本章で言えばmap2sequence, traverseの3つで、これらはいずれもScala用の関数型プログラミングライブラリであるscalazcatsでは提供されている関数です。演習問題を解く上で、挙動や実装を確認

例えばcatssequence関数は以下のように使えます。

package test

import cats.implicits._

@main def sequence() =
  val list1 = List(Some(1), Some(2), Some(3))
  val list2 = List(Some(1), None, Some(3))

  val sequenced1 = list1.sequence
  val sequenced2 = list2.sequence

  println(sequenced1)
  println(sequenced2)

結果は以下のように出力されます。

Some(List(1, 2, 3))
None

また、traverseの実装については、catsの以下のドキュメントに詳しく書かれていますので、分からない時は参考にすると良いでしょう。

typelevel.org

標準ライブラリのEitherについて

本文中で標準ライブラリのEitherの機能についてコラムがありますが、Scala 2.12から変更が入って、本書で解説されているようなRight側に対するflatMapが使えるようになっています。

tototoshi.hatenablog.com