🐕

Functional Programming in C# まとめ

2024/02/13に公開

F#やHaskellを少し勉強していたのですが、C#で関数型プログラミングを扱っている本を見つけたので、内容をまとめていきます。関数型プログラミング未経験だと、F#とうの関数型プログラミング系の言語の書籍などで勉強すると、考え方が全く分からない状態で、さらにコード例も何が書いてあるかわからない、となってしまいがちです。本書はC#で関数型プログラミングをするには?という内容なので、他言語の本よりは入りやすいかと思います(C#の経験があることが前提になりますが)。

まだ、1章のみですが、随時他の章もまとめていく予定です。

1. Intoroducing functional programming

以下、1章の内容をまとめる。

1章で扱う内容は以下、

  • FPの利点と教義
  • C#の関数型の特徴
  • C#における関数の表現
  • 高階関数

1.1. What is this thing called functional programming?

関数型プログラミングは、以下2つの基本的な考え方に基づいている

  • ファーストクラスの値としての関数
  • 状態変化を避ける

関数がファーストクラスの値であるというのはどういうことなのか?というと、intやstringのようなプリミティブ型と同様に扱うことができる、ということ。
変数に関数を割り当てたり、メソッドの引数やメソッドの戻り値としても使うことができる。特に、メソッドの引数や戻り値として関数が利用できることで、強力で簡潔なコードが書けるようになる。

状態変化を避けるとは、一度設定した値は変更しないようする、ということ。どうするかというと、設定した変数を変更するのではなく、新しい値を作りましょう、ということ。

Fuctional vs. object-oriented?

FPとOOPのどちらが良いかというような論争があるが、正しい問題のとらえ方は、FP対命令型である。FPとOOPは直行する概念であり、同時に利用することに支障はない。

1.2. How functional a languege is C#?

C#が言語として、

  • ファーストクラスの値としての関数
  • 状態変化を避ける

をどの程度サポートしているか。

"ファーストクラスの値としての関数"は、非常によくサポートされている。

"状態変化を避ける"については、言語のデフォルトとして、変数が変更できないようになっていてほしいところだが、そうはなっていない。また、変数を変更できないようにするためには努力が必要である。

※本書は、C#6の頃に書かれた本です。

この後、C#6、C#7の関数型プログラミングに関連する新しい特徴をざっと紹介してくれています。個人的には、import staticを知らなかったので勉強になりました。

この本を勉強する直前にF#を少し触っていたのですが、やはり代数データ型が導入されないと、色々辛そうだなぁ、と思っています。

1.3. Thinking in functions

数学の定義としての関数とC#の中での関数の取り扱いを説明する。

数学の関数は、ある集合を別の集合へ写像(マッピング)するもの。

例えば、アルファベットの小文字を対応するアルファベットの大文字へマッピングする関数をかんがえる。この関数の入力と出力の型は

  • char -> char

である。これを関数の型シグネチャと呼ぶ。型シグネチャはある種の契約(守るべき約束事)である。しかし、プログラミングの世界ではこの契約に違反するケースがたくさんある。※この考え方は、後の章で重要になってくると考えられる。

C#で関数を表現することができるものは以下:

  • メソッド
  • デリゲート
  • ラムダ式
  • 辞書

デリゲートに関しては、FuncとActionが追加されて以来、.NETの実装でも使われなくなってきている。しかし、著者の主張では、関数シグネチャに意味を持った名前がつく方が良い場面もある、とのこと。

また、辞書に関しては、関数の数学の定義で説明したように、ある集合を別の集合へ対応させるという意味で関数として利用できる。

1.4. Higher-order functions

"ファーストクラスの値としての関数"の最も重要なメリットである高階関数(Higher-order functions)を見ていく。

  • 引数に関数を取る関数
    • List.SortやLINQのWhereのように関数を引数として取り処理を行う。
    • 例えば、List.Sortは、「2つの要素の比較方法を教えてくれれば、それに従ってソートします」という処理
  • 引数と戻り値が関数である関数
    • 引数として受け取った関数を実行せずに、手を加えて新しい関数を返す
    • 例えば、2つの引数を取る関数を受け取り、その関数の引数の順序を入れ替える汎用的な関数を書くことができる
    • 関数シグネチャを目的に合うように変更できるようになる(OOPのアダプタパターンの関数版)
  • 戻り値が関数である関数
    • 普通の値を引数にとり、関数を返す関数(関数ファクトリ)
    • 例えば、与えられた引数が2で割り切れるかどうかを判定する関数があり、これをnで割り切れるかどうかを判定する関数に一般化したいとする
    • この場合に、最終的な関数に、nと判定したい数を渡すのではなく、まず、nを渡して、関数を作成させるようにする(関数シグネチャが変化しない)

1.5. Using HOFs to avoid duplication

高階関数を使ってコードの重複を削除する具体例の解説。

  1. DBへの接続
  2. DB操作の実行
  3. 接続の解放

例えば、DB操作はこのような手順を毎回踏む必要があるが、これを高階関数を利用して、1と3は一か所だけに記述すればよいようにする。

OOPでも当然できることですが、高階関数を使うことでかなり簡潔に実装できるものなのだと思いました。
著者は、高階関数の欠点として、使いすぎると可読性を損なう場合がある、としていました。

Discussion