🎃

Functional Programming in C# まとめ 5章

2024/02/22に公開

Functional Programming in C#の5章についてまとめています。

4章のまとめは、こちら

5. Designing programs with function composition

この章では以下の内容を扱う

  • 関数合成とメソッドチェーンを使ってワークフローを定義する
  • うまく合成できるように関数を書く
  • ワークフローを使ってサーバリクエストをハンドリングするエンドトゥエンドの例

5.1. Function composition

関数fとgが与えられたとき、これら2つの関数の合成関数hは以下のように定義する:

  • h = f.g

値xにhを適用するのは、同じ値xをgを適用し、その結果をfに適用するのと同じである:

  • h(x) = (f.g)(x) = f(g(x))

C#には関数合成をサポートする特別なシンタックスが存在しないので、関数合成ではなく、メソッドチェーンで実現した方がよい。また、メソッドチェーンを使うことで、関数の実行順序とコードの見た目が一致するので可読性も向上する。

※合成関数(f.g)はgを実行して、その結果を使ってfを実行するので、記述と関数の実行順序が逆になっているように見える。

※※ここで、「関数合成でなくメソッドチェーンを使う」と言っていますが、著者はこの後、メソッドチェーンのことを関数合成と表現しています。

コンテナ(IEnumeralbeやOptionなど)においても、関数合成を行うことは重要。しかし、その場合には、コンテナがfunctor則を満たしている必要がある。functor則は以下:

  • containerとcontainer.Map(id)の結果が等しくなる
  • container.Map(g).Map(f)とcontainer.Map(f.g)の結果が等しくなる

※containerは、IEnumeralbeやOptionなどのコンテナの値、idは恒等関数(x => x)

5.2. Thinking in terms of data flow

プログラム全体を関数合成を使って書くことができる。その場合には、プログラムは単なる関数の集合とデータフローと見なすことができる。

関数を他の関数とより合成しやすくするための特性は以下:

  • 純粋-関数に副作用があると、再利用しづらい
  • 連鎖可能-this引数により、連鎖中で合成できるようにする
  • 一般的-特殊な関数であればあるほど、合成で利用される可能性が低くなる
  • 構造を変化させない―入力がIEnumerableなら出力もIEnumerableなど

5.3. Programming workflows

ワークフローはアプリケーションの要求を理解し表現する強力な方法である。ワークフローは、関数合成を使って設計することができる。ワークフローの各工程は、関数として表現でき、ワークフロー全体全体は、関数合成して得られた関数パイプラインとして表現できる。

一度、関数パイプラインができ上がると、それをリファクタリングしたり、機能を追加したりすることが容易になる。既存のメソッドチェーンの間に新しいメソッドを追加したり、一部をサブメソッドチェーンとしてくくりだしたりが容易にできるからだ。これをより簡単にやるためには、関数合成をしやすくする特性を備えた関数で関数パイプラインを構成しておく。

5.4. An introduction to functional domain modeling

OOPのドメインモデリングとFPのドメインモデリングを極簡単な例で紹介。ポイントは以下

  • OOPのドメインモデリングは、副作用に満ちている
    • インスタンス変数がメンバ変数の値を変更する
    • 例外を投げる
  • FPのドメインモデリングは、関数合成がし易い

個人的には、「例外をなげる」に関しては、OOPとか関係なく設計の問題では?と思いました。

また、「関数合成しやすい」に関しても、「ビジネスワークフローを関数合成で実現する」という設計の大前提があったうえでのお話であり、別の設計方針であれば、必ずしも望ましい特性とは言い切れないのでは?と感じました。

※個人的には、本書を読む前から、もっと低レベルですが著者と似たような方針で実装しており、著者の意見に賛成ですが、コード例や解釈の仕方があまりにも恣意的かな、と感じました。

5.5. An end-to-end server-side workflow

5.4.の実装の続きを行い、完成させる。

5.5.1. Expressions vs. statements

命令型のコードはステートメントに依存し、関数型のコードは式に依存する。

  • ステートメント(文):if文、for文などのプログラムの流れを制御するためのもの

    • 値を持たない
    • 副作用しかない
  • 式:値が使えるところでれば、どこでも使えるもの

    • 値を持つ
    • 副作用があるときもあれば、ない時もある

補足:著者のif文に対する考え

ifを使うことに細心の注意を払っている:1つのifは害がないように思える、しかし、1つifを許すと、追加の要求が来た時に、大量のネストしたifが発生するのを防ぐすべがない。

5.5.2. Declarative vs. imperative

ステートメントを極力使わず、式で表現すると、コードは宣言型になる。宣言型では、コードはワークフローの箇条書きのように読めるようになる。

命令型 宣言型
することをコンピュータに教える;「このリストにこのアイテムを追加して」 したいことをコンピュータに教える;「条件に合致したすべてのアイテムをくれ」
主にステートメントに依存する 主に式に依存する
副作用が遍在する 副作用は自然と式評価の最後に集まる
ステートメントは機械への指示にすんなり変換できる 式を機械への指示に変換する過程はより間接的である(従って、より最適化が可能である)

もう一つ著者が挙げた宣言型のメリットとして、実装が高レベル(に抽象化されたもの)になるので、コード見ただけで実際の動きを理解するのが難しく、単体テストをしないと動きがわからないこと、というものを挙げていました。つまり、必ずテストするようになり、コード読んでわかった気になるのではなく、実際に動かして確かめることに良い効果がある、と。何か主張している内容がものすごく矛盾している気がしてよくわかりませんでした。

5.5.3. The functional take on layering

アプリケーションのレイヤリングに関する話題。

ここに関しても、高レベル、低レベルなどの用語の使い方が、ドメイン層の内部だけの話なのか、アプリケーション全体の話なのか、などが入り混じっているように思えて、今一つ著者の伝えたいことが理解できませんでした。書籍の後半でもう少し詳しい内容を提示してくれるのかもしれませんが。

Discussion