¿Son isomorfos?
Sí, son isomórficos en Haskell. Vea ¿Cuál es la diferencia entre Fix, Mu y Nu en el paquete de esquema de recursión de Ed Kmett para algunos comentarios adicionales?
Si es así, ¿cómo lo demuestras?
Comencemos definiendo funciones para realizar las conversiones:
muToFix :: Mu f -> Fix f
muToFix (Mu s) = s Fix
fixToMu :: Functor f => Fix f -> Mu f
fixToMu t = Mu (\alg -> cata alg t)
Para mostrar que esas funciones son testigos de un isomorfismo, debemos mostrar que:
muToFix . fixToMu = id
fixToMu . muToFix = id
Desde Fix
y hacia atrás
Una de las direcciones del isomorfismo es algo más directa que la otra:
muToFix (fixToMu t) = t
muToFix (fixToMu t) -- LHS
muToFix (Mu (\f -> cata f t))
(\f -> cata f t) Fix
cata Fix t -- See below.
t -- LHS = RHS
El pasaje final anterior, cata Fix t = t
se puede verificar a través de la definición de cata
:
cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg = alg . fmap (cata alg) . unfix
cata Fix t
, entonces, es Fix (fmap (cata Fix) (unfix t))
. Podemos usar la inducción para mostrar que debe ser t
, al menos para un finito t
(se vuelve más sutil con estructuras infinitas; vea el apéndice al final de esta respuesta). Hay dos posibilidades a considerar:
unfix t :: f (Fix f)
está vacío, no tiene posiciones recursivas para cavar. En ese caso, debe ser igual fmap absurd z
para algunos z :: f Void
, y por lo tanto:
cata Fix t
Fix (fmap (cata Fix) (unfix t))
Fix (fmap (cata Fix) (fmap absurd z))
Fix (fmap (cata Fix . absurd) z)
-- fmap doesn't do anything on an empty structure.
Fix (fmap absurd z)
Fix (unfix t)
t
unfix t
no está vacío. En ese caso, al menos sabemos que fmap (cata Fix)
no puede hacer nada más que aplicar cata Fix
en las posiciones recursivas. La hipótesis de inducción aquí es que hacerlo dejará esas posiciones sin cambios. Entonces tenemos:
cata Fix t
Fix (fmap (cata Fix) (unfix t))
Fix (unfix t) -- Induction hypothesis.
t
(En última instancia, cata Fix = id
es un corolario de Fix :: f (Fix f) -> Fix x
ser un álgebra F inicial. Recurrir directamente a ese hecho en el contexto de esta prueba probablemente sería demasiado atajo).
Desde Mu
y hacia atrás
Dado muToFix . fixToMu = id
, para demostrar que fixToMu . muToFix = id
es suficiente probar:
Tomemos la segunda opción y revisemos las definiciones relevantes:
newtype Mu f = Mu (forall a. (f a -> a) -> a)
fixToMu :: Functor f => Fix f -> Mu f
fixToMu t = Mu (\alg -> cata alg t)
fixToMu
ser surjective, entonces, significa que, dado cualquier específico Functor
f
, todas las funciones de tipo forall a. (f a -> a) -> a
pueden definirse como \alg -> cata alg t
, para algunos específicos t :: Fix f
. La tarea, entonces, se convierte en catalogar las forall a. (f a -> a) -> a
funciones y ver si todas ellas pueden expresarse de esa forma.
¿Cómo podríamos definir una forall a. (f a -> a) -> a
función sin apoyarnos fixToMu
? No importa qué, debe implicar el uso del f a -> a
álgebra suministrada como argumento para obtener un a
resultado. La ruta directa lo estaría aplicando a algún f a
valor. Una advertencia importante es que, dado que a
es polimórfico, debemos ser capaces de conjurar dicho f a
valor para cualquier elección de a
. Esa es una estrategia factible siempre f
que existan valores. En ese caso, podemos hacer:
fromEmpty :: Functor f => f Void -> forall a. (f a -> a) -> a
fromEmpty z = \alg -> alg (fmap absurd z)
Para aclarar la notación, definamos un tipo de cosas que podemos usar para definir forall a. (f a -> a) -> a
funciones:
data Moo f = Empty (f Void)
fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo (Empty z) = \alg -> alg (fmap absurd z)
Además de la ruta directa, solo hay otra posibilidad. Dado que f
es un Functor
, si de alguna manera tenemos un f (Moo f)
valor, podemos aplicar el álgebra dos veces, la primera aplicación está debajo de la f
capa externa , vía fmap
y fromMoo
:
fromLayered :: Functor f => f (Moo f) -> forall a. (f a -> a) -> a
fromLayered u = \alg -> alg (fmap (\moo -> fromMoo moo alg) u)
Teniendo en cuenta que también podemos hacer forall a. (f a -> a) -> a
de f (Moo f)
los valores, que tiene sentido para agregarlos como un caso de Moo
:
data Moo f = Empty (f Void) | Layered (f (Moo f))
En consecuencia, fromLayered
se puede incorporar a fromMoo
:
fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo = \case
Empty z -> \alg -> alg (fmap absurd z)
Layered u -> \alg -> alg (fmap (\moo -> fromMoo moo alg) u)
Tenga en cuenta que, al hacerlo, hemos pasado furtivamente de la aplicación alg
bajo una f
capa a la aplicación recursiva alg
bajo un número arbitrario de f
capas.
A continuación, podemos observar f Void
que se puede inyectar un valor en el Layered
constructor:
emptyLayered :: Functor f => f Void -> Moo f
emptyLayered z = Layered (fmap absurd z)
Eso significa que en realidad no necesitamos el Empty
constructor:
newtype Moo f = Moo (f (Moo f))
unMoo :: Moo f -> f (Moo f)
unMoo (Moo u) = u
¿Qué pasa con el Empty
caso fromMoo
? La única diferencia entre los dos casos es que, en el Empty
caso, tenemos en absurd
lugar de \moo -> fromMoo moo alg
. Como todas las Void -> a
funciones son absurd
, tampoco necesitamos un Empty
caso separado :
fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo (Moo u) = \alg -> alg (fmap (\moo -> fromMoo moo alg) u)
Un posible ajuste cosmético es voltear los fromMoo
argumentos, por lo que no necesitamos escribir el argumento fmap
como lambda:
foldMoo :: Functor f => (f a -> a) -> Moo f -> a
foldMoo alg (Moo u) = alg (fmap (foldMoo alg) u)
O, más libre de puntos:
foldMoo :: Functor f => (f a -> a) -> Moo f -> a
foldMoo alg = alg . fmap (foldMoo alg) . unMoo
En este punto, una segunda mirada a nuestras definiciones sugiere que es necesario cambiar el nombre:
newtype Fix f = Fix (f (Fix f))
unfix :: Fix f -> f (Fix f)
unfix (Fix u) = u
cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg = alg . fmap (cata alg) . unfix
fromFix :: Functor f => Fix f -> forall a. (f a -> a) -> a
fromFix t = \alg -> cata alg t
Y ahí está: todas las forall a. (f a -> a) -> a
funciones tienen la forma \alg -> cata alg t
para algunos t :: Fix f
. Por lo tanto, fixToMu
es surjective, y tenemos el isomorfismo deseado.
Apéndice
En los comentarios, se planteó una pregunta pertinente sobre la aplicabilidad del argumento de inducción en la cata Fix t = t
derivación. Como mínimo, las leyes fundadoras y la paramétrica aseguran que fmap (cata Fix)
no creará trabajo adicional (por ejemplo, no aumentará la estructura ni introducirá posiciones recursivas adicionales para profundizar), lo que justifica por qué entrar en las posiciones recursivas es todo lo que importa en el paso inductivo de la derivación. Siendo así, si se t
trata de una estructura finita, el caso base de un vacío f (Fix t)
finalmente se alcanzará, y todo está claro. t
Sin embargo, si permitimos ser infinitos, podemos seguir descendiendo sin cesar, fmap
después fmap
después fmap
, sin llegar nunca al caso base.
Sin embargo, la situación con estructuras infinitas no es tan horrible como podría parecer al principio. La pereza, que es lo que hace viables las estructuras infinitas en primer lugar, nos permite consumir estructuras infinitas perezosamente:
GHCi> :info ListF
data ListF a b = Nil | Cons a b
-- etc.
GHCi> ones = Fix (Cons 1 ones)
GHCi> (\(Fix (Cons a _)) -> a) (cata Fix ones)
1
GHCi> (\(Fix (Cons _ (Fix (Cons a _)))) -> a) (cata Fix ones)
1
Si bien la sucesión de posiciones recursivas se extiende infinitamente, podemos detenernos en cualquier punto y obtener resultados útiles de los ListF
contextos funerarios circundantes . Tales contextos, vale la pena repetir, no se ven afectados fmap
, por lo que cualquier segmento finito de la estructura que podamos consumir no se verá afectado cata Fix
.
Este aplazamiento refleja cómo la pereza, como se menciona en esta discusión otra parte, la pereza colapsa la distinción entre los puntos fijos Mu
, Fix
y Nu
. Sin holgazanería, Fix
no es suficiente para codificar una curión productiva y, por lo tanto, tenemos que cambiar al Nu
mayor punto fijo. Aquí hay una pequeña demostración de la diferencia:
GHCi> :set -XBangPatterns
GHCi> -- Like ListF, but strict in the recursive position.
GHCi> data SListF a b = SNil | SCons a !b deriving Functor
GHCi> ones = Nu (\() -> SCons 1 ()) ()
GHCi> (\(Nu c a) -> (\(SCons a _) -> a) (c a)) ones
1
GHCi> ones' = Fix (SCons 1 ones')
GHCi> (\(Fix (SCons a _)) -> a) ones'
^CInterrupted.