C#コンバイナライブラリ

こんにちは、ガーディアン!

最初の記事では、Combinatorライブラリーのような興味深いことについてお話したいと思います。 すべての議論をできるだけシンプルで理解しやすいものにするようにします。



問題



実際には、問題ではなく、単に何か面白いことを書きたいという願望です。 そして、派生関数を見つけるためのシステムを作成することにしました(この記事は、 C ++の動的マット関数」および「C ++テンプレートを使用した派生関数の計算 の記事と幾分エコーしますが、問題は多少異なり、すべての例はc ++でなくc#にあります。

実際、 何かを準備する欲求はありませんでした。さらに、事業全体の意味が失われます。 そして、私が経験した未熟な心にとっては珍しいアイデアが生まれ、それを実装しました。 少し後に、「Combinatorial library」などの関数型プログラミングのパターンについて誤って読んだときに、それを実装したのは彼であることに気付きました。 それで何ですか?



これは何ですか



コンビナトリアルライブラリは、プリミティブとコンビネーターに分割された関連エンティティのセットです。



また、エンティティのセットでは、 クロージャープロパティが満たされる必要があります。

「複合エンティティは、使用に関してプリミティブと異なるものであってはなりません。」



かなり抽象的に聞こえますか?


数学関数とその導関数の例を見てみましょう。

まず、プリミティブのセットを定義しましょう。

このコンテキストでは、プリミティブは最も単純な関数を作成する必要があります(好みに応じていくつか選択します)。



また、コンビネータのタスクには、加算、乗算、減算、除算などのよく知られた操作が適しています。



どのように機能しますか?


すべてのプリミティブについて、デリバティブ自体を定義します。 次に、コンビネータについて説明します。

コンバイナの「関数の追加」を検討してください。

関数fの導関数と関数gの導関数がわかっている場合、これら2つの関数の合計の導関数を見つけるのは簡単です:(f + g) '= f' + g '。

つまり、ルールを定式化します。関数の合計の導関数は、それらの導関数の合計です。

残りのコンビネーターも同様に定義します。

わかりました。アメリカがまだ発見されていないように、これをコードで表現する方法を理解することは残っています。



練習する



システム全体のキークラスは、抽象クラスFunctionになります。

public abstract class Function { public abstract double Calc(double x); public abstract Function Derivative(); }
      
      





すべてが単純であるため、関数はポイントxで値をカウントでき、その導関数を知ることができなければなりません。



次に、選択した機能を実装します。

まず、定数:

 public class Constant : Function { public Constant(double val) { value = val; } public override double Calc(double val) { return value; } public override Function Derivative() { return new Constant(0); } private readonly double value; }
      
      







関数を線形化するか、その最も単純な形式であるf(x)= xにします。 数学では、このようなマッピングは同一と呼ばれるため、クラスは「Identity」と呼ばれます。

 public class Identity : Function { public override double Calc(double val) { return val; } public override Function Derivative() { return new Constant(1); } }
      
      





すでにここで、これら2つのクラスの接続に気付くことができます。つまり、同一の関数の導関数は定数1です。



それでは、コンビネータのクラスを定義しましょう。 その閉じた性質のため、プリミティブと同じことをすべて実行できる必要があるため、 Functionクラスから継承することは論理的です。

このクラスOperatorを呼び出して、抽象クラスにします。

 public abstract class Operator : Function { protected Operator(Function a, Function b) { leftFunc = a; rightFunc = b; } protected readonly Function leftFunc; protected readonly Function rightFunc; }
      
      





また、例えば、コンビネータ「乗算」を定義し、残りは同様の方法で定義されます。

 public class Multiplication : Operator { public Multiplication(Function a, Function b) : base(a, b) { } public override double Calc(double val) { return leftFunc.Calc(val) * rightFunc.Calc(val); } public override Function Derivative() { return leftFunc.Derivative() * rightFunc + leftFunc * rightFunc.Derivative(); } }
      
      





すべてが明確であることを願っています。1つの関数に2番目の関数を掛けて関数を構築するためのルールを明示的に説明しました。

そのような関数の値をxで見つけるには、xで1つの値を見つけ、次に2番目の値を見つけてから、これらの値を乗算する必要があります。

2つの関数の積の微分は、次のように定義されます:(f * g) '= f' * g + g '* f。 証明:)

コードでは、私たちが言葉で話していることを一つずつ書きました。

かっこいい 私の意見では、はい!



同様に、加算コンビネーター-Additionクラスを定義します。この記事ではコードを提供しません。乗算のコードをほぼ完全に繰り返します。

これらのメソッドを使用して、関数f(x)= 2x + 3を記述してみましょう。

 var f = new Addition(new Multiplication(new Constant(2), new Identity()), new Constant(3)); var x = f.Calc(2) // x   7 var y = f.Derivative().Calc(2) //    2
      
      





わあ、なんてすごい!



デザート



本当にクールではありません。 たとえ最も単純な機能であっても、そのような何かを書かなければならないなら、私は自分自身を撃ちます。 幸いなことに、C#の構文糖は助けになります。



Functionクラスに次のメソッドを追加します。

 public static Function operator +(Function a, Function b) { return new Addition(a, b); } public static Function operator +(double k, Function b) { return new Constant(k) + b; }
      
      





これにより何が得られますか? そして今、代わりに:

 new Addition(new Constant(2), new Identity())
      
      



次のように書くことができます:

 2 + new Identity()
      
      



すでに少し良くなっています。



残りの操作(*、+、-)に対して同様の演算子を定義します。

次に、毎回新しい関数オブジェクトを作成しないように、頻繁に使用する関数を配置する静的クラスを作成します。 このクラスをFuncsと呼びました

また、サイン、コサイン、指数などの関数をいくつか定義しました。 起こったことは次のとおりです。

 public static class Funcs { public static readonly Function Id = new Identity(); public static readonly Function Exp = new Exponenta(); public static readonly Function Zero = new Constant(0); public static readonly Function Sin = new Sinus(); public static readonly Function Cos = new Cosinus(); public static readonly Function Tan = Sin / Cos; public static readonly Function Ctg = Cos / Sin; public static readonly Function Sh = (Exp - 1 / Exp) / 2; public static readonly Function Ch = (Exp + 1 / Exp) / 2; public static readonly Function Tgh = Sh / Ch; public static readonly Function Cth = Sh / Ch; }
      
      





ご覧のとおり、最後の6つの関数を「プリミティブ関数」の組み合わせとして定義しました。



これで、関数f(x)= 2x + 3の決定を再試行できます。

 var f = 2 * Funcs.Id + 3; var x = f.Calc(2) // x   7 var y = f.Derivative().Calc(2) //    2
      
      





まあどう? 私の意見では、そのようなシステムの使用はずっと簡単になりました。



おわりに



このアプローチの長所と短所は何ですか?



明確なプラスは拡張性です。 実際、対数関数などの関数を追加することや、導関数を通じてクラスの一般的なシステムに含めることは難しくありません。



欠点には、関数f(x)= x ^ 100(xの100のべき乗)など、微分を見つけるときの関数の急速な成長が含まれます.10の微分を見つけることは非常に時間のかかる操作です-それが完了するのを待つ忍耐がありませんでした。



参照資料


組み合わせライブラリについて 。 関数型プログラミングに興味のあるすべての人、まず最初にそれに出会った人に言及して記事を提供します-FPの基本的なアプローチについて説明し、例を示します。 私見、記事は正しく構成され、興味深いことに書かれています-読むのは楽しいです。



githubのプロジェクト 。 記事に興味がある場合は、githubからプロジェクトのソースコードをダウンロードできます。そこで、Combinator of Functionsコンビネーターも実装され、区分的に定義された関数が追加され、関数の最も簡単な統合といくつかの最適化が記述されます-一般に、記事に収まらないすべてのもの



PSこの記事をハブで準備している間に、同様のトピックでさらに2つ登場しました。「ダイナミックマット。 C ++の関数 ''および“ C ++のテンプレートを使用した導関数の計算” 、私のものが不必要ではなく、行商人が興味を持ってそれを取ることを願っています。

個人で書くための文法とスペルミスに関するリクエスト。



All Articles