Fix y Mu isomorphic

8

En el recursion-schemespaquete se definen los siguientes tipos:

newtype Fix f = Fix (f (Fix f))

newtype Mu f = Mu (forall a. (f a -> a) -> a)

¿Son isomorfos? Si es así, ¿cómo lo demuestras?

marcosh
fuente
3
Relevante: ¿Cuál es la diferencia entre Fix, Mu y Nu en el paquete de esquema de recurrencia de Ed Kmett (lo único que la respuesta no tiene es el isomorfismo escrito explícitamente).
Duplode
En haskell sí (porque vago) en lenguaje estricto seráMu f < Fix f < Nu f
xgrommx
2
@duplode Acerca de los isomorfismos; Fix-to- Mues esencialmente cata, mientras que Mu-to- Fixes mu2fix (Mu x) = x Fix. La parte difícil es demostrar que estos son inversos mutuos, explotando la parametricidad.
chi
También puedes resolver este kata codewars.com/kata/folding-through-a-fixed-point
xgrommx
1
@xgrommx, en un contexto estricto, ¿cuál es un ejemplo de un término representable por el Fixque no es representable Mu? ISTM Fixdebería ser el más pequeño (intuitivamente porque es una "estructura de datos" y no puede contener fondos)
luqui

Respuestas:

4

¿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 Fixy 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 = tse 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 zpara 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 tno está vacío. En ese caso, al menos sabemos que fmap (cata Fix)no puede hacer nada más que aplicar cata Fixen 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 = ides un corolario de Fix :: f (Fix f) -> Fix xser un álgebra F inicial. Recurrir directamente a ese hecho en el contexto de esta prueba probablemente sería demasiado atajo).

Desde Muy hacia atrás

Dado muToFix . fixToMu = id, para demostrar que fixToMu . muToFix = ides suficiente probar:

  • eso muToFixes inyectivo, o

  • que fixToMues sobreyectiva.

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)

fixToMuser surjective, entonces, significa que, dado cualquier específico Functor f, todas las funciones de tipo forall a. (f a -> a) -> apueden 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) -> afunciones y ver si todas ellas pueden expresarse de esa forma.

¿Cómo podríamos definir una forall a. (f a -> a) -> afunción sin apoyarnos fixToMu? No importa qué, debe implicar el uso del f a -> aálgebra suministrada como argumento para obtener un aresultado. La ruta directa lo estaría aplicando a algún f avalor. Una advertencia importante es que, dado que aes polimórfico, debemos ser capaces de conjurar dicho f avalor para cualquier elección de a. Esa es una estrategia factible siempre fque 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) -> afunciones:

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 fes 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 fcapa externa , vía fmapy 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) -> ade 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, fromLayeredse 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 algbajo una fcapa a la aplicación recursiva algbajo un número arbitrario de fcapas.

A continuación, podemos observar f Voidque se puede inyectar un valor en el Layeredconstructor:

emptyLayered :: Functor f => f Void -> Moo f
emptyLayered z = Layered (fmap absurd z)

Eso significa que en realidad no necesitamos el Emptyconstructor:

newtype Moo f = Moo (f (Moo f))

unMoo :: Moo f -> f (Moo f)
unMoo (Moo u) = u

¿Qué pasa con el Emptycaso fromMoo? La única diferencia entre los dos casos es que, en el Emptycaso, tenemos en absurdlugar de \moo -> fromMoo moo alg. Como todas las Void -> afunciones son absurd, tampoco necesitamos un Emptycaso 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 fromMooargumentos, por lo que no necesitamos escribir el argumento fmapcomo 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) -> afunciones tienen la forma \alg -> cata alg tpara algunos t :: Fix f. Por lo tanto, fixToMues 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 = tderivació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 ttrata de una estructura finita, el caso base de un vacío f (Fix t)finalmente se alcanzará, y todo está claro. tSin embargo, si permitimos ser infinitos, podemos seguir descendiendo sin cesar, fmapdespués fmapdespué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 ListFcontextos 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, Fixy Nu. Sin holgazanería, Fixno es suficiente para codificar una curión productiva y, por lo tanto, tenemos que cambiar al Numayor 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.
duplode
fuente
¿Cómo se justifica cata Fix t = t? Asumir que Fix fes el álgebra inicial fparece un poco un atajo. (La prueba vinculada a la respuesta relacionada parece evitar esto al usar la parametricidad en ambos sentidos.)
Li-yao Xia
No entiendo la prueba de fixToMula surjectivity. "si queremos definir un principio a. (fa -> a) -> una función desde cero" Eso no es lo que queremos. En cambio, vamos k :: forall a. (f a -> a) -> a, tenemos que mostrar eso k = \alg -> cata alg tpara algunos t.
Li-yao Xia
[1/2] @ Li-yaoXia (1) Encendido cata Fix, tenemos cata Fix = Fix . fmap (cata Fix) . unfix. Si tno tiene posiciones recursivas, fmap (cata Fix)no hará nada, y así cata Fix t = Fix (unfix t) = t. Si tiene posiciones recursivas, todo fmap (cata Fix)lo que hará será aplicarles cata Fix, lo que parece suficiente para resolver el problema por inducción.
duplode
[2/2] @ Li-yaoXia (2) Sobre la surjectividad: el argumento es que cualquier posible kdebe obtenerse aplicando directamente el álgebra (para el que f Voidse necesita un valor) o utilizando fmappara aplicarlo recursivamente, y ambos casos pueden expresarse en la forma \ alg -> cata alg t`. Así que creo que he hecho lo que usted sugiere, aunque "desde cero" podría no haber sido la mejor opción de palabras para describirlo.
duplode
1
@ Li-yaoXia Modifiqué el lenguaje que describe el argumento de la surjectividad y agregué la cata Fix t = tderivación (de hecho, dadas las similitudes entre los dos argumentos, ahora siento que presentarlo ayuda a preparar el terreno para la segunda parte de la respuesta) . Gracias por resaltar esos puntos para mejorar.
Duplode