C# によるラムダ計算 (3) 基礎 - 関数合成

[C# シリーズ経由の LINQ]

[C# シリーズによるラムダ計算]

最新バージョン:https://weblogs.asp.net/dixin/lambda-calculus-via-c-1-fundamentals

ラムダ計算シリーズの関数合成について議論するのに最適な場所ではないかもしれません.ただし、関数合成は後の記事で頻繁に使用されるため、ここでは簡単に紹介します。

機能構成

関数合成とは、単純な関数をより複雑な関数に結合することを意味します。 f1 と f2 の合成は、次のように定義されます。 f2 ∘ f1。この新しい関数のアプリケーションは次のとおりです:

(f2 ∘ f1) x := f2 (f1 x)

ここで、関数名 f1 および f2 は、適用される順序を意味します。 f2 ∘ f1 ​​は、f1 の後に f2 と読むこともできます。

繰り返しますが、最初の関数の出力を 2 番目の関数の入力として使用することにより、2 つの関数の適用を連鎖させることは完全に自然なことです:

double x = 1;
double y = Math.Sqrt(Math.Abs(x));

以下は、2 つの単純な関数を組み合わせた、より複雑な関数です:

Func<double, double> absAndSqrt = x => Math.Sqrt(Math.Abs(x));

したがって、absAndSqrt は Math.Abs​​ と Math.Sqrt を合成したものです。

通常、Func 型の関数と Func 型の関数は、Func 型の新しい関数に合成できます。

public static partial class FuncExtensions
{
    public static Func<T1, T3> o<T1, T2, T3>
        (this Func<T2, T3> function2, Func<T1, T2> function1) => 
            arg => function2(function1(arg));
}

残念ながら、C# にはカスタム関数演算子を定義する場所がないため、拡張メソッドを使用する必要があります。このメソッドは、 ∘ 演算子を模倣するために o と名付けられています。また、ラムダ計算では関数がカリー化されているため、この 1 つの拡張メソッドで十分です。

他の言語の組み込み演算子

他の関数型言語には、関数合成演算子が組み込まれているのが一般的です。 Haskell では、∘ は単なるドット (.) です:

(.) :: (b -> c) -> (a -> b) -> a -> c
f2 . f1 = \x -> f2 (f1 x)

F# には>>:

があります
let inline (>>) f1 f2 x = f2 (f1 x)

順合成といいます。したがって、後方合成演算子 <<:

もあります
let inline (<<) f2 f1 x = f2 (f1 x)

プロパティ

関数合成には 2 つの重要な特性があります

結合性

関数合成は連想的です。つまり、(f3 ∘ f2) ∘ f1 ​​と f3 ∘ (f2 ∘ f1) は同じです。

x を (f3 ∘ f2) ∘ f1 ​​に適用すると、∘ の定義に従って:

  ((f3 ∘ f2) ∘ f1) (x)
≡ (f3 ∘ f2) (f1 (x))
≡ f3 (f2 (f1 (x)))

x を f3 ∘ (f2 ∘ f1) に適用すると:

  f3 ∘ (f2 ∘ f1)
≡ f3 ∘ (f2 (f1 (x)))
≡ f3 (f2 (f1 (x)))

したがって、それらは同一の結果につながります。 C# では、これは f3.o(f2).o(f1) と f3.o(f2.o(f1)) が同じであることを意味します。

ユニット

関数合成用のユニット関数があります:

Id := λx.x

そのため:

f ∘ Id ≡ f

そして

Id ∘ f ≡ f

C# では、ID は

public static partial class FuncExtensions
{
    public static T Id<T>
        (T value) => value;
}
です。