【感想・ネタバレ】なっとく!関数型プログラミングのレビュー

\ レビュー投稿でポイントプレゼント / ※購入済みの作品が対象となります
レビューを書く

感情タグBEST3

Posted by ブクログ

# 1周め 読み終えた
通常、本を読んだだけでプログラミングの能力があがったりするものではない。そのことは理解しているつもりだけど、この本に関しては当てはまらないかもしれない。そう思わされるほど読み進めるごとに何かが変わっていくような、もっと大げさに言うと、新たな世界が見えてくるような、そんな内容だった。もっと昔に、Haskellとかに取り掛かる前に読むことが出来ていたら、違った道を選択していたかもしれない。おそすぎるということもなく、これからどういう風に関数型プログラミングと向き合っていくかを一度ちゃんと考えたほうが良さそうだ。本気で関数型言語をメインにしようという気持ちはまったくなかった。かといって無視して放置しておくわけにもいかない。そんな微妙な気持ちでいたところ、もっと積極的に取り組んでも良いと後押ししてくれてるような感じがする。

この本ではScalaを使っている。ただし、Scalaについての本ではなく、Scalaの経験を前提としないし、別の関数型言語にも共通する、関数型プログラミングの本質的なところを学ぶとしている。自分の場合は、念の為別の本でScalaの初歩的なことを学んでから取り組んだ。その効果十分に合ったと思われ、かなりスムーズに読み進めることが出来たと思っている。なので、本当に初歩的なところだけでもいいので、先に学んでおいて、Scalaのコードに足を引っ張られないようにしておいたほうが効果的なように思う。Part 3 (11章と12章)は真剣に関数型言語を使おうとしている人のためのもので、それらを除くと、命令形の言語で関数型プログラミングをスパイス的に使う上でも役に立つテクニックや概念が満載だった。よっぽど関数型プログラミングの素質があるのでもない限り、本格的に取り組む前に読んでおくと学習が捗るだろうことが期待できる。

## 12章を読んだ ➤ 関数型プログラムをテストする
最終章。なんか妙に難しいと思っていたら、途中に11章と12章は上級者向けだと書かれていた。ついていくだけならそこまでとは思わないが、実践しようとするなら話は別で、相当な熟練度が必要な上級者向けの内容に間違いない。ただ単にテスティングフレームワークを使ってみるだけのものではない。これまで学んできた関数型プログラミングの手法を総動員して、正しいプログラムはどうあるべきかを追求する。テスト対象には11章のWikidataからデータを取得するプログラムを用いている。純粋関数をテストするケースや、副作用のある関数をテストするケース、外部のサービスにアクセスしないといけないケース、それらを可能な限り関数型の手法で扱っている。最後にはTDDに従って、新たな機能を追加するところまでやっている。最後の最後には、かなり難易度が高めの実習問題が用意されている。今までの実習問題にはすべて解答が用意されていたのだけど、ここだけはは用意されていない。11章と12章をきっちり理解していたのなら、問題なく解答できるものだったのだろう。今回はそこまでは到達できなかったとして、未着手のままにしておいた。


## 11章を読んだ ➤ 関数型プログラムを設計する
かなり難しい。これまでの総まとめのような内容。現実的なアプリケーションを作成しながら、どれだけ関数型プログラミングが身に付いたかをチェックすることができる。作成するのはWikidataからデータを取得して、観光名所のガイドを返すプログラム。要件は今までよりずっと複雑になっている。「まず動かし、正しく動かし、その上で高速に動かす」というアドバイスに従って、順に進められる。Wikidataというのは初めて聞いた。SPARQLというSQLに似ていないこともないようなクエリ言語に対し、結果をやや謎めいたエンコードされたようなフォーマットで返してくる。このサービスを利用するために、Apache Jenoというフレームワークを利用する。これは命令型のライブラリであるが、関数型のScalaでも上手く統合できることを示す例となっている。

今まではかなり順調に進んできた感触を得ていたけど、最後近くに来てようやく壁にぶつかった感じになった。特に難しいわけではないが、ちゃんと理解できているのか、引っ掛かりを覚える。この章をスムーズにこなせないということは、おそらく全体として何かが欠けているような気がしてならない。その上、どこが欠けているのかもはっきりと自覚できなくて、なんとも言えない気分になった。


## 10章を読んだ ➤ 並行プログラム
関数型プログラミングで並行処理について学ぶのは初めてだ。並行プログラミングで最も問題となる共有状態へのアクセスについて、関数型プログラミングはどのような答えを出しているのだろう。関数型プログラミングはイミュータブルな値しか扱わない。そのような制約の中で、共有状態への更新はどう考えればいいのだろうか。この本では、アトミック参照という手法をとっている。いくつかの手順を踏むことで、安全に値を更新することができる。もうこの段階まで来ると、IOやStreamは遠慮なく、当たり前のように使われている。順番に読み進めていなければ、この章を理解するのは不可能だっただろう。ちゃんとついていけるのが不思議なくらいで、読み始める前は想像も出来なかった。

ScalaにはAkkaというライブラリがある。これはアクターモデルであり、状態の可変性を基礎とするため、純粋関数の利点が損なわれてしまうと書かれている。Scala歴1週間にも満たない自分でも聞いたことがあるくらい有名なので、使うかどうかは別にして、いずれそちらもちゃんと触っておきたい。


## 9章を読んだ ➤ 値としてのストリーム
ストリームによるプログラミングには全く馴染みがなかった。前章のIOをさらに押し進めた発展的な内容となっている。また、これまでの関数型プログラミングのツールを総動員している。これまでのことをきっちり理解してなければ厳しいものになるだろう。逆に考えると、この章でつまずかなかったのなら、これまでのことをちゃんと理解できてきたのだと自信につながる。今に限ったことではないが、この本はまず例題として作成するプログラムの要件を提示して、それを関数型プログラミングの視点から分析する。そして、関心事を章の見出しに合ったものに絞り込んでいく。そのステップがあまりにも洗練されすぎていて、驚かされることが多々ある。確かに、書かれていることはなっとくできる。しかし、現実でこんなにきれいに問題を解決することができるのだろうかと思わされるところがなくもない。この章について言えば、あまりにもストリームがジャストフィットしている。関数型プログラミングの習熟度がどの程度になれば、これほど完璧な解が出せるのか、見当がつかない。なっとくするだけで終わるのではなく、できれば現実の問題に適用できるだけのものが欲しい。もちろん、なっとくもできなければその先に進むことも出来ない。まずはなっとくすることであり、そのための役割は十分すぎるほど果たしている。まずはなっとくすること、後は練習あるのみといったところか。


## 8章を読んだ ➤ 値としてのIO
副作用のある処理、信頼できない純粋でない関数をどうするか。

かなり手こずった。内容がどうこうという以前に、Scalaの環境を整えるのに手こずった。これまでと違い、信頼できないAPIをシミュレートするのに、Javaのコードが提供されていて、それをScalaから利用できるようにしなければならなかった。本書のサンプルコードはsbtのプロジェクトとして提供されている。そのままではREPLが使いにくいので、工夫する必要があった。最終的には上手くいって、しっかりと実習することができる環境が整った。

Scalaの標準ライブラリではIOモナドが提供されていない。この本ではCats Effectという外部ライブラリを利用している。このことが何を意味するのか、もしかしたら、通常のScalaのプログラミングではさほど副作用がある関数に対して神経質にならなくて良い場合があるのかと疑いを持った。しかし、この本は徹底して関数型プログラミングの流儀に従う。関数型プログラミングは純粋関数でイミュータブルな値を変換するプログラミングだ。副作用のある信頼できない関数をIOという値に閉じ込めてしまう。例えIOの中の処理が副作用を起こすものであっても、このIO自体は副作用を持たない。したがって、このIOを返す関数も、値を変換する関数も純粋なままである。最終的にはどこかでIOが実行されなければならない。Cats EffectのIOではunsafeRunSyncという関数を呼び出すことで、IOが実行される。この呼び出しだけが、これまでのIOに閉じ込めてきた副作用のある処理を実際に行うところとなる。つまり、副作用の発生するポイントをある一点にまとめてしまうこととなる。IOで記述した処理は、この呼び出しまで遅延する。本当にこれで関数を純粋に保つ原則を守ったことになるのだろうか。狐につままれたような気分だ。しかし、本当にそうなのだろう。経験を積めばきっとその価値がわかるようになると期待したい。


## 7章を読んだ ➤ 型としての要件
この章は関数型プログラミングにおける設計の章だといえる。要件とは何か。プログラムに要求される機能とでも言えばいいのだろうか。これこれができるように、一方こちらはこうでなければいけないといったもの。この章では、音楽アーティスト、メタリカとかツェッペリンの簡単なデータをモデル化して、検索するプログラムを開発していく。最初は、Stringを中心にプリミティブな型だけを使用して、とりあえず動くものを作る。それでは何が問題なのかを掘り下げながら、少しずつ保守の面でも使用する上でも扱いやすくなるように改良していく。最終的には、Stringなどのプリミティブな型はほぼ姿を消して、堅牢なプログラムに改造していく。その中で最も大きな役割を果たすのが代数的データ型、略してADTと呼ばれるもので、この章のもう一つの中心的な話題だった。

平たく言ってしまうと、関数の引数の型がStringとかだと何を表しているのかわからないし、何でも放り込めてしまう。もっと関心の対象と直結した新たな型を独自で定義して、守備範囲を狭めていくのが良い。関連性のあるデータは一つまとめた新たな型を定義する。そのためのツールがnewtypeやADTとなっている。この章ではエラー処理を全く行っていない。それは手を抜いているからではなく、エラーが発生し得ない設計になっているからだった。良い設計ができるようになりたければ、何かを設計してみる必要がある。それも大量に。練習あるのみ、というアドバイスが書かれている。これからプログラムを書くときは、常に意識をしていこうと思う。


## 6章を読んだ ➤ エラー処理
Optionを徹底的に学ぶ。前章までの、特にflatMapで習得した考え方をベースに、一歩々々丁寧に考え方を広げていく。最終的にはOptionをEitherに置き換えて、エラーの内容を伝搬させることができる理想的といっていいようなエラー処理が出来上がる。OptionからEitherへの切り替えの例示は見事としか言いようがない。

OptionはRustに大々的に導入されていているので、少しは馴染みがあるつもりでいた。そんなことは全然なくて、本当の価値を全く知らずにいたことをはっきりと認識することが出来た。RustのOptionはまず間違いなく、関数型言語を由来とするものだろう。関数型言語で本物のOptionを知ることは、Rustでの重要なエラー処理の設計にもきっと良い影響を与えることになる。また、多くの主流の言語でもライブラリでOptionのようなものをサポートするようになってきている。もしかしたら、エラー処理は例外からOptionに完全にシフトすることができるのかもしれない。今後のプログラミングの考え方に大きな影響を与える極めて価値の高い内容だった。


## 5章を読んだ ➤ 逐次プログラム
flatMapのための章。この関数はmapと入れ子になったListなど平坦にするflattenを同時に行う関数だ。flatMapは関数型プログラミングにおいてもっとも重要な関数であると位置づけている。そんなことは思いもしなかった。そもそも、この章のタイトルである逐次プログラムとなんの関係があるのかも分からずにいた。読み進めていくと、確かにflatMapが逐次的な処理を行うために重要な役割を果たすであろうことがわかってきた。

Listを例に取ると、まず、flatMapを連鎖させる必要性があるところから始まる。次に、2つ目以降のflatMapで、それより前のコンテキストで出現した中間値にアクセスする必要性があるケースが多発する。そのとき、flatMapを入れ子にすることで対処できる。しかし、入れ子にしていくと、命令型のコードの入れ子になったループのような見苦しいコードになってしまう。そこで、for内包表記という糖衣構文が用意されている。この構文を使うと、遥かに読みやすいコードになる。コンパイラは非常に賢く、for内包表記をコンパイル時にflatMapの入れ子に置き換えるそうだ。何も知らずにfor内包表記を使うことは可能ではあるが、この事実を知らずにいることは危険が伴う。もう一つ重要なのは、命令型言語のforループとは全く違うということだ。for内包表記は間違いなく式であり、値に評価される。そもそもflatMapと同じものであるのだから、当然だとも取れる。

最後に、Optionをfor内包表記で処理する手法が示されている。Listほどはっきりとした感触が掴めない。次の章がエラー処理なので、そのための準備でもあるのだろう。後でちゃんと理解できるようになることを期待したい。


## 4章を読んだ ➤ 値としての関数
かなり長い章だった。Javaは徐々に影を薄め、Scalaが存在感を増してきた。関数を引数として取る関数、関数を返す関数、関数を返す関数を返す関数、まとめると高階関数を学ぶ。sortBy、map、filter、foldLeftを通して、高階関数がどれだけうまく問題を解決できるかを体験することができる。関数を返す関数を返す関数は、そのままでは扱いやすいとは言えない。そこで、他の関数型プログラミング言語と同じように、Scalaにも強力なカリー化の能力が備わっている。関数のパラメーターを分離することによってそれが可能となる。Haskellがデフォルトでカリー化を行うのに比べると、専用の記法を使わないといけないのはやや冗長な感じはする。しかし、十分に洗練されていて、自分のような関数型プログラミングの入門者にとってはScalaの方が脳みそに優しい。もしもっと早くこの本と合わせてScalaに手を付けていたのなら、関数型プログラミングの学習もはかどっていたのにと惜しむところがある。


## 3章を読んだ ➤ イミュータブルな値
関数プログラミングとは何かを「イミュータブルな値を操作する純粋関数を使うプログラミングである。」と定めている。もし本当にこれだけで片付くならどんなに楽だっただろう。現実はそんなに甘くなく、このプログラミングのルールを貫くには多くの困難を乗り越えて、厳しいトレーニングを積まないといけないことは想像に難くない。でも、今のところはそんなに難しい話は登場しない。前章と同じように、命令型のコードにありがちな落とし穴を例に上げて、イミュータブルな値しか扱わない関数型プログラミングならそれを完全に回避できることを強調する。それだけ、イミュータブルであることは重要だということだ。最後に、イミュータブルなリストに慣れるための実習がある。Scalaは少し学習済みなので楽勝だった。しかし、この程度の問題が解ける程度では、現実のプログラミングに立ち向かうのはまず無理なことはよくわかっている。もっともっと先にいかないといけない。まだまだ序盤なので、この本を読み終える頃には少しでもそこに近づけていることを期待したい。


## 2章を読んだ ➤ 純粋関数
なぜ純粋関数が良いのかを手取り足取り教えてくれる。命令型のコードに潜む問題を提示するためにまだJavaのコードが多く登場する。一方、徐々にScalaへ移行しつつある。


## 1章を読んだ ➤ 関数型プログラミングを学ぶ
別の本でScalaの初歩を一通り学んだので、読み始めることにした。第1章は、準備段階と行ったところ。この本を読むのに必要な予備知識や、どのように取り組んでいくべきかが書かれていた。Scalaに興味を持ったきっかけが、この本のメインの言語であったことにある。実際に読んでみると、Scalaの予備知識は全く必要ないとしている。しかし、今ちょうどScalaの初歩を学び終えた状態で読んでみると、明らかにすんなりとコードや書いてあることの理解が進んでいるように思える。手を付ける順序は間違っていなかったと思える。

0
2024年03月02日

「IT・コンピュータ」ランキング