9
  • 壊れたコード

    public static partial class LogicExtensions {
        public static bool Implies<T>(this T premise, T conclusion) {
            return conclusion.Infers(premise);
        }
    
        public static bool Infers<T>(this T premise, T conclusion) {
            return premise.Implies(conclusion);
        }
    }
    

上記のコードは、次のことを表現することを期待しています。

前提は結論を意味するため、結論は前提を推測します。

結論は前提を推測するため、前提は結論を意味します。

それは循環論法であり、間違いなくスタックオーバーフローを引き起こします。次に、次のように再設計します。

  • 作業コード

    public delegate bool Paradox<T>(T premise, T conclusion, Paradox<T> predicate=null);
    
    public static partial class LogicExtensions {
        public static bool Implies<T>(this T premise, T conclusion, Paradox<T> predicate=null) {
            if(null==predicate)
                return conclusion.Infers(premise, Implies);
    
            if(Infers!=predicate)
                return predicate(premise, conclusion);
    
            return LogicExtensions.Implies(conclusion as IConvertible, premise as IConvertible);
        }
    
        public static bool Infers<T>(this T premise, T conclusion, Paradox<T> predicate=null) {
            if(null==predicate)
                return premise.Implies(conclusion, Infers);
    
            if(Implies!=predicate)
                return predicate(premise, conclusion);
    
            return LogicExtensions.Implies(conclusion as IConvertible, premise as IConvertible);
        }
    
        static bool Implies<T>(T premise, T conclusion) where T: IConvertible {
            var x=premise.ToUInt64(null);
            return x==(x&conclusion.ToUInt64(null));
        }
    }
    

しかし、それは次のことを意味します。

  1. Paradox<T>私が最初に名前を付けたものなしでは行けないという正しいロジックで失敗しますがPredicate<T>、と競合しSystem.Predicate<T>ます。

  2. 前者のコードとは異なり、T実装しなければならないのは欠陥です。IConvertable

明確にするために、私はコードを機能させるだけでなく、論理式のように表現して、T実装の制約なしに論理について推論するためにさらに再利用できるようにしようとしていますIConvertable。ロジックを正しくして、欠陥のあるデザインを取り除く方法はありますか?

4

2 に答える 2

9

あなたの質問から、あなたが何をしようとしているのかはあまり明確ではありません。C#でいくつかの論理述語を表現しようとしていますか?あなたは論理について推論するコードを書き込もうとしていますか?論理式を表現しようとしていますか?

パラドックス。計算におけるパラドックスについて話すときは、ラムダ計算とラッセルのパラドックスについて読むのが良いかもしれません(ここに素晴らしい記事があります)。ラムダ計算は本質的に単純な関数型プログラミング言語です(ラムダ関数とアプリケーションを備えたC#を想像してみてください。ただし、他には何もありません)。

これは(コンピューターが発明される前に)数学の基礎となるシステムとして最初に開発されましたが、意味のない再帰的な計算を書くことができたため(詳細は記事を参照)、実際には機能しませんでしたが、書くことはできます次のように評価される計算(C#表記):

r(r) = not(r(r)) = not(not(r(r)))

...そしてx = r(r)そのようなものがx = not(x)ないので、モデルは数学の基礎として意味がありません。ただし、再帰的な計算を記述できるプログラミング言語のモデルとしては便利ですが、終了することはありません。

ロジックを表す。プログラムで論理式を表現したい場合は、式の表現を推論から分離したいと思うでしょう。これは関数型言語(F#など)で行うのが最適ですが、C#でも(入力を増やすだけで)行うことができます。数式のF#表現は次のようになります。

type Formula = 
  | Variable of string
  | Negation of Formula 
  | Implies of Formula * Formula

数式は、変数(名前付き)または別の数式の否定、あるいはある数式が別の数式を暗示する含意のいずれかであるという考え方です。C#では、クラス階層と同じものを表すことができます(Formula基本クラスと3つの派生クラスとして)。

次に、数式を操作するメソッドとして推論を実装できます。F#では、これはパターンマッチングを使用して非常に簡単に実行できます。C#では、おそらく型テストを使用して、引数がであるかどうかをチェックするコードを作成する必要がありますVariable(次に何かを実行します...)。引数がNegation(それから何かをする...)などの場合。

于 2013-03-27T12:31:20.547 に答える
2

IConvertibleを削除する

「簡単な部分」から始めましょう:を削除しIConvertibleます。これが必要な理由は、このコードをすべての型で機能させたいためです。つまり、特定のメンバー(Implies)があることに常に影響を与えることができるとは限りません。あなたがやりたいのは、彼らがC ++で呼んでいるものです:テンプレートの特殊化ですが、残念ながらC#では(まだ?)利用できません:

    static bool Implies<T>(T premise, T conclusion) where T : IConvertible
    {
        var x = premise.ToUInt64(null);
        return x == (x & conclusion.ToUInt64(null));
    }

    static bool Implies<T>(T premise, T conclusion) where T : Foobar
    {
    // other fancy logic
    }

// and so on

これを解決する最も簡単な方法は、マルチメソッドを使用することです。これには「dynamic」キーワードを使用できます。

public partial class Implications
{
    internal static bool CheckImplies<T>(T lhs, T rhs)
    {
        return Implies((dynamic)lhs, (dynamic)rhs);
    }

    public static bool Implies(int lhs, int rhs)
    {
        return lhs == (lhs & rhs);
    }
    // your other implies thingies implement this same partial class
}

public static partial class LogicExtensions
{
    public static bool Implies<T>(this T premise, T conclusion, Paradox<T> predicate = null)
    {
        if (null == predicate)
            return conclusion.Infers(premise, Implies);

        if (Infers != predicate)
            return predicate(premise, conclusion);

        return Implications.CheckImplies(premise, conclusion);
    }

    public static bool Infers<T>(this T premise, T conclusion, Paradox<T> predicate = null)
    {
        if (null == predicate)
            return premise.Implies(conclusion, Infers);

        if (Implies != predicate)
            return predicate(premise, conclusion);

        return Implications.CheckImplies(premise, conclusion);
    }
}

また、「3番目の」メソッドがある場合は、単にそれを呼び出すことができます

私は奇妙な再帰的定義を数分間見てきましたが、それは私にはあまり意味がありません...とにかく3番目のヘルパーメソッドがある場合は、それを直接呼び出してみませんか?:-)

    public static bool Implies<T>(this T premise, T conclusion)
    {
        return Implications.CheckImplies(premise, conclusion);
    }

    public static bool Infers<T>(this T premise, T conclusion)
    {
        return Implications.CheckImplies(conclusion, premise);
    }

not(not(T))問題

上記は私にはあまり意味がありませんでしたが、型システムと言語を使用して少し手助けするのは完全に合理的だと思います。まあ、確かにあなたはそれをすることができます、そしてこれは私がそれをする方法です... :-)

ジェネリックを使用して「Not」クラスを導入しましょう。

public class Not<T>
{
    public Not(T val)
    {
        this.not = val;
    }
    internal T not;
}

ここでNot>の状況が発生した場合は、提供します。それ以外の場合は、直接使用します。まあ、私たちはいくつかの拡張機能でそれを非常に簡単に行うことができます:

    public static T Optimize<T>(this Not<Not<T>> var)
    {
        return Optimize(var.not.not);
    }

    public static T Optimize<T>(this T var)
    {
        return var;
    }

それをテストするために、あなたは同様のことをすることができます:

    var val = new Not<Not<int>>(new Not<int>(2));
var result = val.Optimize();

これは、過負荷解決によって正しいOptimize呼び出しが選択されるため、機能します。これにより、Not>>>>をT値などに最適化できます。

'Not'をラッパークラスでラップしてから、型システムを有利に使用するため、これも機能します。

元の問題に戻る

「Implies」と「Infers」を直接評価する代わりに、一時オブジェクトを使用して邪悪な作業を行ってみませんか。演算子のオーバーロード(正確には暗黙的な変換)を使用して、暗黙と推論の関係を指定できます。唯一の欠点は、拡張メソッドに制限があることです。

C#演算子のオーバーロードは、最適な方法を選択します。前者の場合、これは完全に一致し、後者の場合、メソッドは暗黙的に変換され、その後Evaluateが呼び出されます。言い換えれば、評価が怠惰になるという理由だけで、スタックオーバーフローは発生しません。コードの準備はできましたか?:-)

public class Implies<T>
{
    public Implies(T premise, T conclusion)
    {
        this.premise = premise;
        this.conclusion = conclusion;
    }

    public T premise;
    public T conclusion;

    public static implicit operator Infers<T>(Implies<T> src)
    {
        return new Infers<T>(src.conclusion, src.premise);
    }
}

public class Infers<T>
{
    public Infers(T premise, T conclusion)
    {
        this.premise = premise;
        this.conclusion = conclusion;
    }

    public T premise;
    public T conclusion;

    public static implicit operator Implies<T>(Infers<T> src)
    {
        return new Implies<T>(src.conclusion, src.premise);
    }
}

public static partial class LogicExtensions
{
    public static Implies<T> Implies<T>(this T premise, T conclusion)
    {
        return new Implies<T>(premise, conclusion);
    }

    public static Infers<T> Infers<T>(this T premise, T conclusion)
    {
        return new Infers<T>(premise, conclusion);
    }
}

public class Foo
{
    // The things you wish to implement :-)
    public static bool Evaluate(Implies<int> impl)
    {
        return impl.premise == (impl.conclusion & impl.premise);
    }

    static void Main(string[] args)
    {
        Implies<int> impl= 0.Implies(2); // will be called directly
        Infers<int> impl2 = 0.Infers(2); // will be converted

        Console.WriteLine("Res: {0} {1}", Evaluate(impl), Evaluate(impl2));

        Console.ReadLine();
    }
}
于 2013-04-08T15:02:02.727 に答える