El isomorfismo se apresura al rescate

El "isomorfismo" es uno de los conceptos básicos de las matemáticas modernas. Usando ejemplos concretos en Haskell y C #, no solo explicaré la teoría para los no matemáticos (sin usar ningún símbolo y término matemático oscuro), sino que también mostraré cómo se puede usar esto en la práctica diaria.


El problema es que la igualdad estricta (por ejemplo, 2 + 2 = 4) suele ser demasiado estricta. Aquí hay un ejemplo:


Haskell
add :: (a, a) -> a add (x, y) = x + y 

C #
 int Add(Tuple<int, int> pair) { return pair.Item1 + pair.Item2; } 

Sin embargo, hay una forma más, más complicada y en muchas situaciones mucho más práctica, de definir la misma función en cierto sentido :


Haskell
 add' :: a -> a -> a add' x = \y -> x + y 

C #
 Func<int, int> Add_(int x) { return y => x + y; } 

Contrariamente al hecho obvio de que para dos x, y ambas funciones siempre devolverán el mismo resultado, no satisfacen la igualdad estricta:


  • la primera función devuelve inmediatamente la cantidad (es decir, realiza el cálculo en el momento de la exportación),
  • mientras que la segunda función devuelve otra función (que al final devolverá la suma; si alguien la llama, por supuesto, de lo contrario no se realizará ningún cálculo: este es un ejemplo de cálculo retrasado y también hay un isomorfismo aquí, al que volveré) un poco mas tarde).

Y esto es "ser demasiado estricto".


El isomorfismo es "bastante estricto"; no requiere una igualdad completa que lo abarque todo, sino que se limita a la igualdad "en cierto sentido", que siempre está determinada por un contexto específico.


Como puede suponer, las dos definiciones anteriores son isomorfas. Esto significa exactamente lo siguiente: si solo me dan uno de ellos, entonces ambos me lo dan implícitamente : todo gracias al isomorfismo, un convertidor bidireccional de uno a otro . Resumiendo un poco los tipos:


Haskell
 curry :: ((a, b) → c) → a → b → c curry fxy = f (x, y), uncurry :: (a → b → c) → (a, b) → c uncurry f (x, y) = fxy 

C #
 Func<TArg1, Func<TArg2, TRes>> Curry(Func<Tuple<TArg1, TArg2>, TRes> uncurried) { return arg1 => arg2 => uncurried(Tuple.Create(arg1, arg2)); } Func<Tuple<TArg1, TArg2>, TRes> Uncurry(Func<TArg1, Func<TArg2, TRes>> curried) { return pair => curried(pair.Item1)(pair.Item2); } 

... y ahora para cualquier x, y :


Haskell
 curry add $ x, y = uncurry add' $ (x, y) 

C #
 Curry(Add)(x)(y) = Uncurry(Add_)(Tuple.Create(x, y)) 

Un poco más de matemática para los especialmente curiosos

De hecho, debería verse así:


Haskell
 curry . uncurry = id uncurry . curry = id id x = x 

C #
 Compose(Curry, Uncurry) = Id Compose(Uncurry, Curry) = Id, : T Id<T>(T arg) => arg; Func<TArg, TFinalRes> Compose<TArg, TRes, TFinalRes>( Func<TArg, TRes> first, Func<TRes, TFinalRes> second) { return arg => second(first(arg)); } ...  extension- (  Id   ): Curry.Compose(Uncurry) = Id Uncurry.Compose(Curry) = Id, : public static Func<TArg, TFinalRes> Compose<TArg, TRes, TFinalRes>( this Func<TArg, TRes> first, Func<TRes, TFinalRes> second) { return arg => second(first(arg)); } 

Id debería entenderse como "no pasó nada". Dado que el isomorfismo es un transformador de dos vías por definición, siempre puedes 1) tomar una cosa, 2) convertirla en otra y 3) volver a convertirla en la primera. Solo hay dos operaciones de este tipo: porque en la primera etapa (N ° 1), la elección es de solo dos opciones. Y en ambos casos, la operación debería conducir a exactamente el mismo resultado, como si nada hubiera sucedido (es por esta razón que está involucrada la igualdad estricta, porque nada ha cambiado en absoluto, y no "algo" no ha cambiado).


Además de esto, existe un teorema de que el elemento id siempre es único. Tenga en cuenta que la función Id es genérica, polimórfica y, por lo tanto, verdaderamente única con respecto a cada tipo en particular.


El isomorfismo es muy, muy útil precisamente porque es estricto, pero no demasiado. Conserva ciertas propiedades importantes (en el ejemplo anterior, el mismo resultado con los mismos argumentos), al tiempo que le permite transformar libremente las estructuras de datos (portadores de comportamiento y propiedades isomórficas). Y esto es absolutamente seguro, porque el isomorfismo siempre funciona en ambas direcciones, lo que significa que siempre puede regresar sin perder esas "propiedades importantes". Daré otro ejemplo que es tan útil en la práctica que incluso apuntala muchos lenguajes de programación "avanzados" como el de Haskell:


Haskell
 toLazy :: a -> () -> a toLazy x = \_ -> a fromLazy :: (() -> a) -> a fromLazy f = f () 

C #
 Func<TRes> Lazy(TRes res) { return () => res; } TRes Lazy(Func<TRes> lazy) { return lazy(); } 

Este isomorfismo conserva el resultado del cálculo diferido en sí mismo: esta es la "propiedad importante", mientras que las estructuras de datos son diferentes.


La conclusión? OOP, especialmente fuertemente tipado, (forzado) funciona al nivel de "igualdad estricta". Y por lo tanto, a raíz de los ejemplos anteriores, a menudo es demasiado estricto. Cuando te acostumbras a pensar "demasiado estrictamente" (y esto sucede imperceptiblemente: se filtra en el programador, especialmente si no está buscando inspiración en matemáticas), tus decisiones pierden involuntariamente la flexibilidad deseada (o al menos objetivamente posible). Comprender el isomorfismo: en una comunidad con un intento consciente de estar más atento al propio
y código extranjero: ayuda a definir más claramente el círculo de "propiedades importantes", abstrayendo de detalles innecesarios: a saber, de estructuras de datos específicas en las que se imprimen estas "propiedades importantes" (también son "detalles de implementación"). En primer lugar, esta es una forma de pensar, y solo entonces: soluciones (micro) arquitectónicas más exitosas y, como consecuencia natural, un enfoque revisado para las pruebas.


PD: Si veo que el artículo se ha beneficiado, volveré a los temas de "soluciones (micro) arquitectónicas más exitosas" y "un enfoque revisado para las pruebas".

Source: https://habr.com/ru/post/es436756/


All Articles