c# - tipos - falacias todas
La falacia informal causa el desbordamiento de la pila (2)
Código roto
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); } }
El código anterior espera expresar que:
La conclusión infiere la premisa, es porque la premisa implica la conclusión.
La premisa implica la conclusión, es debido a la conclusión que infiere la premisa.
Sería un razonamiento circular , y definitivamente causará el desbordamiento de la pila. Luego lo rediseño de la siguiente manera:
Código de trabajo
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)); } }
Pero eso significa:
Es un error en la lógica correcta que no puede pasar sin
Paradox<T>
que originalmente llaméPredicate<T>
pero en conflicto conSystem.Predicate<T>
.Es defectuoso que
T
deba implementarIConvertable
diferencia del formador de código.
¿Hay alguna manera de corregir la lógica y deshacerse del diseño defectuoso?
actualizar:
Para que quede claro, estoy tratando de hacer que el código no solo funcione, sino que también represente como fórmulas lógicas que puedo reutilizarlo para razonar sobre la lógica sin una restricción de T
implementa IConvertable
.
No está muy claro por tu pregunta, ¿qué estás tratando de hacer? ¿Estás tratando de expresar algunos predicados lógicos en C #? ¿Estás tratando de escribir un código que razonará sobre la lógica? ¿Estás tratando de representar fórmulas lógicas?
Paradojas. Cuando se habla de paradojas en los cálculos, podría ser bueno leer sobre el cálculo lambda y la paradoja de Russel ( aquí hay un buen artículo ). El cálculo lambda es esencialmente un lenguaje de programación funcional simple (imagine C # con funciones y aplicaciones lambda, pero nada más).
Primero se desarrolló como un sistema para la base de las matemáticas (antes de que se inventaran las computadoras), pero esto realmente no funcionó porque usted era capaz de escribir cálculos recursivos que no tenían sentido (vea el artículo para más detalles), pero puede escribir un cálculo que se evalúa de la siguiente manera (en notación C #):
r(r) = not(r(r)) = not(not(r(r)))
... y dado que no hay x = r(r)
tal que x = not(x)
, el modelo no tiene sentido como fundamento de las matemáticas. Pero es útil como modelo de lenguajes de programación donde puede escribir cálculos recursivos, aunque es posible que nunca terminen.
Representando la lógica. Si desea representar fórmulas lógicas en su programa, probablemente desee separar la representación de la fórmula del razonamiento . Esto se hace mejor en lenguajes funcionales (como F #), pero también se puede hacer en C # (solo con más tipeo). La representación F # de una fórmula sería algo así como:
type Formula =
| Variable of string
| Negation of Formula
| Implies of Formula * Formula
La idea es que una fórmula sea una variable (nombrada) o una negación de otra fórmula o una implicación donde una fórmula implica otra. En C #, puede representar lo mismo que una jerarquía de clases (con Formula
como clase base y tres clases derivadas).
Su razonamiento puede ser implementado como un método que manipula fórmulas. En F #, esto se puede hacer bastante fácilmente usando la coincidencia de patrones. En C #, probablemente necesitarás usar pruebas de tipo para escribir código que verifique si el argumento es Variable
(luego haz algo ...); si el argumento es Negation
(entonces haz algo ...) etc.
Dejar caer IConvertible
Comencemos con la ''parte fácil'': IConvertible
el IConvertible
. La razón por la que lo necesita es porque quiere que este código funcione en todos los tipos, lo que significa que no siempre puede influir en que tiene un determinado miembro ( Implies
). Lo que le gustaría hacer es lo que llaman en C ++: especialización de plantillas, pero desafortunadamente no está disponible (¿todavía?) En 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
La forma más fácil de resolver esto es usar multimétodos. Puede usar la palabra clave ''dinámica'' para esto:
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);
}
}
Y si tienes un ''tercer'' método, simplemente puedes llamarlo
He estado buscando un par de minutos en la extraña definición recursiva y realmente no tiene sentido para mí ... si tiene un tercer método auxiliar, ¿por qué no simplemente llamarlo directamente? :-)
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);
}
El problema de no (no (T))
Si bien lo anterior no tenía mucho sentido para mí, me parece perfectamente razonable utilizar el sistema de tipos y el lenguaje para ayudarlo un poco. Bueno, seguramente puedes hacer eso y así es como lo haría ... :-)
Vamos a introducir una clase ''No'' con un genérico:
public class Not<T>
{
public Not(T val)
{
this.not = val;
}
internal T not;
}
Si tenemos una situación Not> aquí, queremos dar; de lo contrario, queremos usarla directamente. Bueno, podemos hacerlo bastante fácil con algunas extensiones:
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;
}
Para probarlo, puedes hacer algo similar:
var val = new Not<Not<int>>(new Not<int>(2));
var result = val.Optimize();
Esto funciona, porque la resolución de sobrecarga elegirá la llamada de Optimize correcta, lo que garantiza que optimizará el valor Not >>>> en el valor T, y así sucesivamente.
También funciona, porque envolvemos el ''No'' en una clase contenedora y luego usamos el sistema de tipo para nuestra ventaja.
Volviendo al problema original
En lugar de evaluar directamente ''Implicaciones'' e ''Infers'', ¿por qué no utilizar un objeto temporal para hacer su mal trabajo? Puede usar la sobrecarga del operador (conversión implícita para ser preciso) para especificar cómo se relacionan Implicaciones e Infers. La única pega es que tiene sus límites con los métodos de extensión.
La sobrecarga del operador C # elegirá el mejor método de coincidencia. En el primer caso, esta será la coincidencia exacta, en el segundo caso, el método se convertirá implícitamente y luego se llamará a Evaluar. En otras palabras, no se acumulará desbordamiento, simplemente porque hará que su evaluación sea floja. Listo para el código? :-)
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();
}
}