Grokking Functional ProgrammingはScalaを使ってFunctional Programming (FP)の意義、FPの実現方法、テストの仕方など幅広くカバーしている本。 ソースコード多めでかなり丁寧に説明されていた印象。
以下気になったところをまとめ。
なぜPure Functionが重要か?
状態の管理する責務とと状態から値を算出する責務を両方持ったクラスを例にとって、Pureではない関数が存在するため誤った値を算出してしまう問題点を指摘。 それぞれの責務を分けPureにすることで不具合を解消することができる、というところから有用性を説いている。 読み過ごしたかもしれないがテストの容易性についてもここで軽く触れてほしかった感はある。
immutableでない(かもしれない)関数は危険。例えばJavaのListはmutableなため関数のシグネチャを見るだけではimmutableかはわからず実装も確認する必要がある。でないと呼び出し方によっては意図せず値が変更されて不具合につながる。 一方でScalaのListやStringはimmutableがnativeサポートされているので安心。
Stream
Streamを使うことによってProviderとConsumerを粗結合にできるメリットがある。やりとりするデータの型さえ合意しておけばよく、データをどう使うかや何回データが送られてくるかお互い知る必要がない。
おもしろかったのが、Streamを用いてAPIコールを一定間隔で実行するときに一定間隔に値をgenerateするStreamを別で用意してAPIコール用のStreamとzipしていた点。 zipしたあと必要なのはAPIコール用のほうだけなのだがなんとzipしたあと片方だけ返してくれるutlity関数(zipLeft, zipRight)がscalaには用意されていて本当にFP向けに考えられているなと感じた。
Concurrent programs
並行実行も宣言的に表現することで、値として扱えてる点がおもしろい。 宣言的に表現するために重要な要素として、Refという並行でミュータブルにアクセスできるな型がある。これはcompare-and-swap (CAS; 楽観的排他とほぼ同値と考えていい?)を提供しているAtomicReferenceのラッパーであり、更新に失敗した場合にリトライするところまでやってくれる優れもの。 スレッドセーフなデータ型としてConcurrentHashMapがある。使えるケースがかなり限定的という記載があったが具体的なことはわからなかった。
CASはCPUサイクルを多く使用するためパフォーマンスに影響があるが、パフォーマンスがUXに大きく影響を与えないないのであればデータの整合性でビジネスにインパクトを与えてしまうリスクを考えて、多少の犠牲を払ってでもRefのような並行性をサポートしたプリミティブを使用すべき。
無限に実行されるプログラムを表現するのに再帰を使っているが、なぜか?i 再帰を使った無限実行のヘルパーとしてforeverMという関数がある。
テストについて
- Property based Test vs Example based test
- Exmaple based Test: 手動でテストケースのパターンを実装する手法
- Property based Test: 制約を提示するだけで具体的なテストケース生成はツールに任せる方法。Example based Testだとパターンを考える必要があるが、いいパターンが思いつかないときはProperty based Testを勧めている。
- SUTは単一責務を持つように設計しSUTの責務に絞ってテストすべき。
- データを何らかの方法で取得し、「整形すること」が責務であればデータを取得する部分はモックし整形する部分だけをテストする
- データを取得することが責務な関数であれば実サービス (DBなど)を用意してIntegration Testする
- Integaration TestでもIOやResourceオブジェクトを使ってリーダブルかつメンテ性のあるテストを実現すする