2013-03-27 19 views
9
  • łamany kodNieformalne błąd powoduje przepełnienie stosu

    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); 
        } 
    } 
    

Powyższy kod spodziewa się wyrazić:

Zawarcie wywodzi się z założenia, ze względu na założenie implikuje wniosek.

Założenie implikuje konkluzję, ponieważ wniosek opiera się na założeniu.

Będzie to circular reasoning, a na pewno spowoduje przepełnienie stosu. I wtedy przeprojektować go w następujący sposób:

  • kod roboczych

    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)); 
        } 
    } 
    

Ale to oznacza:

  1. To nie na właściwej logiki, że nie może przejść bez Paradox<T> który Początkowo nazwałem Predicate<T>, ale jest konflikt z System.Predicate<T>.

  2. Jest uszkodzony, aby T musiał wdrożyć IConvertable w przeciwieństwie do poprzedniej wersji kodu.

Żeby było jasne, ja staram się, aby kod nie tylko działa, ale również reprezentują podobne logicznych formuł, które mogę dalej używać go do rozsądku o logice bez ograniczeń z T narzędzi IConvertable. Czy istnieje sposób na poprawną logikę i pozbycie się wadliwego projektu?

+11

[* Paradox *] (http://www.veterangamers.co.uk/blog/wp-content/uploads/2011/04/paradox.jpg) ... –

+4

To na w tym momencie prawdopodobnie przerzucę się na Prolog. – spender

+0

@spender: To dawno temu, kiedyś napisałem w Prologu. Może odpowiedź z Prologu na rozwiązanie tego problemu? –

Odpowiedz

9

Z twojego pytania nie wynika jednoznacznie, co próbujesz zrobić. Czy próbujesz wyrazić niektóre predykaty logiczne w języku C#? Czy próbujesz napisać kod, który będzie uzasadniał logikę? Czy próbujesz reprezentować formuły logiczne?

Paradoksy. Mówiąc o paradoksach w obliczeniach, dobrze byłoby przeczytać o rachunku lambda i paradoksie Russela (here is a nice article). Rachunek lambda jest zasadniczo prostym językiem programowania funkcjonalnego (wyobraź sobie C# z funkcjami lambda i aplikacją, ale nic więcej).

Został on opracowany jako system dla fundamentu matematyki (przed komputery zostały wynalezione), ale to naprawdę nie działa, ponieważ byli w stanie napisać cyklicznych obliczeń, że nie ma sensu (patrz artykuł szczegóły), ale można napisać obliczeń że ocenia się następująco (w notacji # C):

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

... a ponieważ nie ma x = r(r) takie, że x = not(x) model nie ma sensu jako fundament matematyki. Jest jednak użyteczny jako model języków programowania, w których można pisać rekursywne obliczenia - choć one nigdy się nie kończą.

Reprezentowanie logiki. Jeśli chcesz reprezentować formuły logiczne w swoim programie, prawdopodobnie chcesz oddzielić reprezentację wzoru od argumentu . Najlepiej zrobić to w językach funkcjonalnych (np. F #), ale możesz to zrobić także w języku C# (wystarczy wpisać więcej tekstu). F # reprezentacja formule byłoby coś jak:

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

Chodzi o to, że formuła jest albo zmienna (nazwa) lub negacji innego wzoru lub implikacja gdzie jedna formuła zakłada inny. W języku C# można reprezentować to samo, co hierarchia klas (z Formula jako klasą podstawową i trzema klasami pochodnymi).

Twoje rozumowanie może zostać zaimplementowane jako metoda manipulująca formułami. W języku F # można to zrobić całkiem łatwo za pomocą dopasowywania wzorców. W języku C#, prawdopodobnie będziesz musiał użyć testów typu, aby napisać kod, który sprawdza, czy argument jest Variable (następnie zrób coś ...); Jeśli argument jest Negation (wtedy coś zrobić ...) itd.

2

spada IConvertible

Zacznijmy z 'łatwego części': upuszczenie IConvertible. Powodem tego jest to, że chcesz, aby ten kod działał na wszystkich typach, co oznacza, że ​​nie zawsze możesz wpływać na to, że ma on określonego użytkownika (Implies). Co chcesz zrobić, to co nazywają w C++: template specjalizacji, ale niestety nie jest dostępne w języku C# (jeszcze?):

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 

Najprostszym sposobem rozwiązania tego jest użycie multimethods. Można użyć „dynamiczne” słowo kluczowe dla tego:

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); 
    } 
} 

A jeśli masz metodę „trzeci”, można po prostu nazwać

Szukałem kilka minut w dziwna rekursywna definicja i nie ma to dla mnie większego sensu ... jeśli i tak masz trzecią metodę pomocy, dlaczego nie nazwać jej bezpośrednio? :-)

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 (nie (T)) Problem

Chociaż powyższe nie ma większego sensu dla mnie, uważam, że całkowicie uzasadnione, aby korzystać z systemu typu i języka aby ci pomóc. Cóż, na pewno można to zrobić i to w jaki sposób to zrobić ... :-)

Niech wprowadzi „nie” klasę z Generic:

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

Jeśli mamy nie> sytuacja tutaj chcemy dać - w przeciwnym razie chcemy użyć bezpośrednio.Cóż, możemy to zrobić dość łatwo z niektórymi rozszerzeniami:

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; 
    } 

By to sprawdzić, można zrobić coś podobnego:

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

To działa, ponieważ rozdzielczość przeciążenie odbierze poprawnego połączenia optymalizacji co zapewnia, że ​​zoptymalizujesz Not >>>> na wartość T i tak dalej.

Działa również, ponieważ zawijamy "Not" w klasie otoki, a następnie używamy systemu typu na naszą korzyść.

Wracając do pierwotnego problemu

Zamiast bezpośrednio oceny „zakłada” i „wnioskuje”, dlaczego nie korzystać z tymczasowego obiektu, aby zrobić swoją złą pracę. Możesz użyć przeciążania operatorów (domyślna konwersja jest precyzyjna), aby określić relacje Implies i Infers. Jedynym haczykiem jest to, że ma swoje granice dzięki metodom rozszerzania.

Przeciążenie operatora C# wybierze wtedy najlepszą metodę dopasowania. W pierwszym przypadku będzie to dokładne dopasowanie, w drugim przypadku metoda zostanie domyślnie przekonwertowana, a następnie zostanie wywołana funkcja oceniania. Innymi słowy, nie będzie on stosował przepełnienia, tylko dlatego, że zrobi swoją leniwą ocenę. Gotowy na kod? :-)

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(); 
    } 
} 
+0

Właśnie czytałem, a nie jeszcze test. Wygląda dobrze, ale bardzo przypomina obejście, czy mam rację? –

+1

@KenKin No tak i nie ... Tak, w tym sensie, że specjalizacja generyczna nie jest po prostu obsługiwana, więc potrzebujesz jakiegoś obejścia ... który jest typem pomocniczym, który pomaga trochę kompilatorowi. Nie w tym sensie, że nie oceniam bezpośrednio wartości, ale używam klasy pomocniczej do odroczenia operacji. – atlaste

Powiązane problemy