タイトルで言うほどScala3関係無いですね。単に読書メモだと思ってください。
例外を使わないエラー処理
ソフトウェアの複雑度を示すメトリクスに「循環的複雑度」というのがあります。
簡単に言うと条件分岐やループが有ると数値が上がっていって、一定の数値を超えるとコードを把握することが困難となり、バグが混入する確率が上がっていく、という考え方です。
この計測方法が適切か?閾値は適切か?というのは組織文化に依るものなので、パラメータの調整をしながら適用していくのが適切ですが、おおまかな考え方については非常に同意できる考え方ですね。
初めてOption
型を知った時に、この「循環的複雑度」が下がる仕組みだな、と感じました。Option型にmapなどを適用するとき、Some
ならば処理が先に進み、None
ならば何も行われません。fold
メソッドを使えばそれぞれに対する処理が、if文なしにかけます。ただ、本質的には分岐は存在するのでテストはしないといけないので、イデオムで書かれることにより「読み解く脳の負荷が下がる」というメリットは有っても本質的な複雑度は変わらない(理解のしやすさは違う)というところがポイントでしょう。
下限境界
本文でも触れられていますが、orElse
メソッドの型シグニチャはちょっと複雑な指定がされています。これは、下限境界と呼ばれる指定です。
def orElse[B>:A](ob: => Option[B]): Option[B]
コレクション系のライブラリを自分で書く時に気をつけないといけない点ですね。公式ドキュメントに詳しく解説があります。
map2, sequence, travesableについて
本書の解説や、演習問題にはScalaの標準ライブラリには無い関数がいくつか出てきます。例えば、本章で言えばmap2
、sequence
, traverse
の3つで、これらはいずれもScala用の関数型プログラミングライブラリであるscalaz
やcats
では提供されている関数です。演習問題を解く上で、挙動や実装を確認
例えばcats
のsequence
関数は以下のように使えます。
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
の以下のドキュメントに詳しく書かれていますので、分からない時は参考にすると良いでしょう。
標準ライブラリのEitherについて
本文中で標準ライブラリのEither
の機能についてコラムがありますが、Scala 2.12から変更が入って、本書で解説されているようなRight側に対するflatMap
が使えるようになっています。