なっとく!関数型プログラミングを読んだ。
https://www.shoeisha.co.jp/book/detail/9784798179803
動機
-
熱狂的な信者がいることが気になっていた
- 一方で広く受け入れられているというほどではない(気がする)
- 定義や説明についての議論が定期的に起こっている
- これはオブジェクト指向でも同じか
-
読むまでのイメージではIOの多いWebアプリケーションなどにどう対応していくのかの想像がつかなかった
- データ指向プログラミングを読んだら変更のコミットを一箇所にまとめる、とかって書いてあったので
- データ指向プログラミングと関数型が違うことは書いてあったが、じゃあ関数型ではどこでコミットするんだろうとか
- データ指向プログラミングを読んだら変更のコミットを一箇所にまとめる、とかって書いてあったので
-
Railsの置き換え先としてScalaが使われている例をよく目にする
- Railsのつらさを解消する要素が関数型にはあり、取り入れられる部分もあるのではと思った
感想とか
-
本について
- めちゃめちゃ良い本だった
- 飛躍がないし、前のページまでに出てきたことのみを使ってくれるので読んでいて安心感がある
- 手を動かすパートがあって定着を支援してくれる
- Scala未経験だったので動かすのに苦労する部分もあった
- 環境構築は自分でやれみたいなスタンス
- Consoleからファイルをうまく読めないとか、バージョンが違って関数が見つからないとか
- ChatGPTがだいたい解決してくれた
- 平日1時間、休日2時間やって20日ぐらいかかった
- 一番最後の挑戦問題だけやってないけど…
- めちゃめちゃ良い本だった
-
オブジェクト指向と関数型は対立する概念じゃないっぽい
- Scalaではクラスあるしインスタンスもある。誕生日をメンバにして隠しつつ年齢だけ取れるようにするとかもできる
- クラスあるのでコンストラクタにバリデーション書ける
- enumとパターンマッチを使えばポリモーフィズムみたいなこともできて、より使い勝手がよい
- 結局差分プログラミングとか実現したいことは変わらないので、似たようなソリューションが出るのは当然か
- オブジェクト指向を学ぶときについてくるSOLID原則とかの概念は関数型でも使えるので、全く1から学び直さないといけないというわけではない
- RubyやJSでも関数型の考え方が取り入れられているのでそんなにビビる必要ない
- Scalaではクラスあるしインスタンスもある。誕生日をメンバにして隠しつつ年齢だけ取れるようにするとかもできる
-
Scalaが思ってたより分かりやすい
- 他の言語を学んだときよりもSyntaxErrorが出る頻度が圧倒的に少なかった
- 型の書き方がTypeScriptに似てるからかも
- 末尾にreturnを書かないのはRubyと共通しているが、型があるだけで全然印象が違う
- Rubyはシグネチャがないので戻り値が使われるか、nilを返すかどうかもパッと分からなくてつらい
- 他の言語を学んだときよりもSyntaxErrorが出る頻度が圧倒的に少なかった
-
関数型を学ぶときは無理にJavaScriptとかの既存の言語で解説されたものを読むより、言語ごと学んだほうがいい
- JSで書かれた関数型プログラミングの基礎を先に読んだが、カリー化された関数の呼び出しのネストが深すぎたり、パターンマッチの実装が直感的じゃなかったりして、何でこんな面倒なことをしなくちゃいけないのかが気になって内容が頭に入ってこなかった
- 最終的に低レイヤで実行されるコードが命令的だったとしても、コード上では値を宣言的に記述できるというのがポイントなので、便利な構文が用意された言語を使わないとメリットが見えてこない
- 文字の長さを取るときに、命令型ではforでChar一つずつをカウントするけど宣言型では
length
だけでいいんです、という記述を読んだときは欺瞞だろと思ったが、抽象化が1段上なんですよ、ということで理解した
- 文字の長さを取るときに、命令型ではforでChar一つずつをカウントするけど宣言型では
-
関数型言語ではすべてが純粋関数なのかなと思っていたが、そうでもない様子(Scalaでは)
- 遅延評価を使って副作用の発生を実行まで遅らせる + モナドで副作用のある処理そのものを値として扱うことによって、システムから純粋な関数を最大限切り出して関数型コアとすることが大事
- うまく切り出せないと無駄に非純粋な関数が増えるので設計スキルは結局必要
- 副作用のある処理を動かすところがどこかでは必要で、それはmain関数とかWebアプリならControllerだったりするのかもしれない
- 遅延評価を使って副作用の発生を実行まで遅らせる + モナドで副作用のある処理そのものを値として扱うことによって、システムから純粋な関数を最大限切り出して関数型コアとすることが大事
-
IOとかEitherが便利そうすぎる
- こんなんあったら何でもできるやんと思ったけどストリームの特定の要素を使ってコンソールに文字列を出力する、みたい単純なことでも結構頭を捻ったので、人を選びそうだなとは思った
-
次関連書籍を読むならScala関数型デザイン&プログラミングにしたい
読んでたときのメモ
-
例外を投げる関数は非純粋関数
- charsAtは引数だけに基づいてるし、何回実行しても同じ結果が返ってくるけどと思ったけど例外を投げること自体がプログラムの制御構造に影響を与えるので非純粋として扱われるよう
-
共有かつミュータブルな状態、がプログラムを複雑にする
- FPは不変で、OOPはカプセル化で対抗
-
パターンマッチングで漏れなく記述されていることをコンパイラが確かめてくれるの便利そう
- TypeScriptでも似たようなことができるらしい
-
ScalaではシングルクォートだとCharでダブルクォートだとString
-
p.127 リスト内の整数の最大値を返す式を書くときfoldLeftの初期値にInt.MinValueを使う
-
p.153 文はプログラムの内部状態を変更しなければ意味がない
-
p.163 for内包表記で戻り値の方は最初の列挙子によって定義される
-
p.168 for内包表記でOption型の変数がNoneを返したとき、すぐNoneが返る
-
p.201 orElseでfallbackを簡単に書ける
-
p.221 すべてを値にして表現するのが関数型
-
p.254 直積 + 直和 = 代数型データ構造
-
p.300 副作用のある関数自体を引数として渡すことで関数型コアから依存をなくす
-
p.310 List[IO[A]]をIO[List[A]]に変換する
-
p.324 zipはINNER JOINみたいなもん
-
p.360 遅いストリームとzipすることでストリームの要素の生成タイミングをコントロールできる
-
p.370 scanはアキュムレータが計算されるたびに値を出力する
-
p.388 無限ループを表すIO[Nothing]型
- Refはイミュータブルなポインタみたいなもん
-
p.403 関数のシグネチャをインターフェースとして捉えることで実装を遅らせることができる
-
p.412 QuerySelection自体はミュータブルなので、それを扱う関数は非純粋関数になる
-
p.421 Resource.useはRubyのtransactionブロックみたいなやつ
-
p.446 すべてのテストを関数型で記述することはできず、E2Eテストは命令型でカバーする必要がある
-
p.468 collectはfilterした上で型を変えることができる