La Applicativeclase de tipos representa functores monoidales laxos que preservan la estructura monoidal cartesiana en la categoría de funciones escritas.
En otras palabras, dados los testigos de isomorfismos canónicos que (,)forman una estructura monoidal:
-- Implementations left to the motivated reader
assoc_fwd :: ((a, b), c) -> (a, (b, c))
assoc_bwd :: (a, (b, c)) -> ((a, b), c)
lunit_fwd :: ((), a) -> a
lunit_bwd :: a -> ((), a)
runit_fwd :: (a, ()) -> a
runit_bwd :: a -> (a, ())
La clase de tipos y sus leyes se pueden escribir de la misma manera:
class Functor f => Applicative f
where
zip :: (f a, f b) -> f (a, b)
husk :: () -> f ()
-- Laws:
-- assoc_fwd >>> bimap id zip >>> zip
-- =
-- bimap zip id >>> zip >>> fmap assoc_fwd
-- lunit_fwd
-- =
-- bimap husk id >>> zip >>> fmap lunit_fwd
-- runit_fwd
-- =
-- bimap id husk >>> zip >>> fmap runit_fwd
Uno podría preguntarse cómo se vería un functor que es oplax monoidal con respecto a la misma estructura:
class Functor f => OpApplicative f
where
unzip :: f (a, b) -> (f a, f b)
unhusk :: f () -> ()
-- Laws:
-- assoc_bwd <<< bimap id unzip <<< unzip
-- =
-- bimap unzip id <<< unzip <<< fmap assoc_bwd
-- lunit_bwd
-- =
-- bimap unhusk id <<< unzip <<< fmap lunit_bwd
-- runit_bwd
-- =
-- bimap id unhusk <<< unzip <<< fmap runit_bwd
Si pensamos en los tipos involucrados en las definiciones y leyes, se revela la verdad decepcionante; OpApplicativeNo es una restricción más específica que Functor:
instance Functor f => OpApplicative f
where
unzip fab = (fst <$> fab, snd <$> fab)
unhusk = const ()
Sin embargo, aunque cada Applicativefunctor (realmente, cualquiera Functor) es trivial OpApplicative, no necesariamente hay una buena relación entre las Applicativelaxitudes y las OpApplicativeoplaxidades. Entonces podemos buscar functores monoidales fuertes con la estructura monoidal cartesiana:
class (Applicative f, OpApplicative f) => StrongApplicative f
-- Laws:
-- unhusk . husk = id
-- husk . unhusk = id
-- zip . unzip = id
-- unzip . zip = id
La primera ley anterior es trivial, ya que el único habitante del tipo () -> ()es la función de identidad ().
Sin embargo, las tres leyes restantes, y por lo tanto la propia subclase, no son triviales. Específicamente, no todos Applicativeson una instancia legal de esta clase.
Aquí hay algunos Applicativefunctores para los cuales podemos declarar instancias legales de StrongApplicative:
IdentityVoidF(->) r(ver respuestas)Monoid m => (,) mVec (n :: Nat)Stream(infinito)
Y aquí hay algunos Applicatives para los que no podemos:
[]Either eMaybeNonEmptyList
El patrón aquí sugiere que la StrongApplicativeclase es en cierto sentido la FixedSizeclase, donde "tamaño fijo" * significa que la multiplicidad ** de habitantes de aun habitante de f aes fija.
Esto se puede expresar como dos conjeturas:
- Cada
Applicativerepresentación de un contenedor de "tamaño fijo" de elementos de su argumento tipo es una instancia deStrongApplicative - No
StrongApplicativeexiste ninguna instancia en la que el número de ocurrencias deapueda variar
¿Alguien puede pensar en contraejemplos que refuten estas conjeturas, o en algún razonamiento convincente que demuestre por qué son verdaderos o falsos?
* Me doy cuenta de que no he definido correctamente el adjetivo "tamaño fijo". Lamentablemente, la tarea es un poco circular. No conozco ninguna descripción formal de un contenedor de "tamaño fijo", y estoy tratando de encontrar uno. StrongApplicativeEs mi mejor intento hasta ahora.
Sin embargo, para evaluar si esta es una buena definición, necesito algo para compararla. Dada alguna definición formal / informal de lo que significa para un functor tener un tamaño o multiplicidad dada con respecto a los habitantes de su argumento tipo, la pregunta es si la existencia de una StrongApplicativeinstancia distingue con precisión los functores de tamaño fijo y variable.
Al no tener conocimiento de una definición formal existente, hago un llamamiento a la intuición en mi uso del término "tamaño fijo". Sin embargo, si alguien ya conoce un formalismo existente para el tamaño de un functor y puede compararlo StrongApplicative, mucho mejor.
** Por "multiplicidad" me refiero en un sentido laxo a "cuántos" elementos arbitrarios del tipo de parámetro del functor ocurren en un habitante del tipo de codominio del functor. Esto es sin tener en cuenta el tipo específico al que se aplica el functor y, por lo tanto, sin tener en cuenta los habitantes específicos del tipo de parámetro.
No ser preciso sobre esto ha causado cierta confusión en los comentarios, así que aquí hay algunos ejemplos de lo que consideraría el tamaño / multiplicidad de varios functores:
VoidF: fijo, 0Identity: fijo, 1Maybe: variable, mínimo 0, máximo 1[]: variable, mínimo 0, máximo infinitoNonEmptyList: variable, mínimo 1, máximo infinitoStream: fijo, infinitoMonoid m => (,) m: fijo, 1data Pair a = Pair a a: fijo, 2Either x: variable, mínimo 0, máximo 1data Strange a = L a | R a: fijo, 1
fuente

(->) rson y son isomórficos de la manera correcta.(->) r; necesita los componentes del isomorfismo para preservar la fuerte estructura aplicativa. Por alguna razón, laRepresentableclase de tipo en Haskell tiene unatabulate . return = returnley misteriosa (que realmente ni siquiera tiene sentido para los functores no monádicos), pero nos da 1/4 de las condiciones que necesitamos decirtabulateyzipson morfismos de una categoría adecuada de monoides. . Las otras 3 son leyes adicionales que debes exigir.tabulateyindexson morfismos de una categoría adecuada ..."returnno es un problema grave.cotraverse getConst . Constes una implementación predeterminada parareturn/pureen términos deDistributive, y, dado que los distributivos / representables tienen una forma fija, esa implementación es única.Respuestas:
No estoy seguro de esa primera conjetura, y según las discusiones con @AsadSaeeduddin es probable que sea difícil de probar, pero la segunda conjetura es cierta. Para ver por qué, considere la
StrongApplicativeleyhusk . unhusk == id; es decir, para todosx :: f (),husk (unhusk x) == x. Pero en Haskell,unhusk == const ()para que la ley es equivalente a decir para todosx :: f (),husk () == x. Pero esto a su vez implica que solo puede existir un valor de tipo distintof (): si hubiera dos valoresx, y :: f (), entoncesx == husk ()yhusk () == y, entoncesx == y. Pero si solo hay unf ()valor posible ,fdebe ser de forma fija. (por ejemplodata Pair a = Pair a a, para , solo hay un valor de tipoPair (), este esPair () (), pero hay múltiples valores de tipoMaybe ()o[()]).husk . unhusk == idimplica quefdebe ser de forma fija.fuente
f ()" implica "que el número de ocurrencias deano puede variar" en presencia de GADT de fantasía y esas cosas?ano puede variar" no es una condición suficiente para unaStrongApplicativeinstancia; por ejemplo,data Writer w a = Writer (w,a)tiene multiplicidad no variable dea, pero no es aStrongApplicative. Realmente necesitas que la forma del functor sea invariable, lo que creo que es una consecuencia def ()ser un singleton.f ()" implica "el número de ocurrencias deano puede variar". Estoy objetando que el último paso de ese argumento no es claramente cierto; por ejemplo, considerardata Weird a where One :: a -> Weird a; None :: Weird Bool. Hay un valor distinto de tipoWeird (), pero diferentes constructores tienen un número variable deas en su interior. (No es un contraejemplo completo aquí porqueFunctores difícil, pero ¿cómo sabemos que no se puede arreglar?)Weird ()es un singleton pero no tiene una forma fija. PeroWeirdno es unFunctor, así que no puede ser un deStrongApplicativetodos modos. Supongo que la conjetura relevante sería: sifes aFunctor, ¿f ()ser un singleton implica queftiene una forma fija ? Sospecho firmemente que esto es cierto, pero como has notado, todavía no tengo ninguna prueba.Podemos responder al menos una de estas preguntas en forma negativa:
De hecho, uno de los ejemplos de un legal
StrongApplicativeen la pregunta original es incorrecto. El escritor aplicativoMonoid => (,) mno lo esStrongApplicative, porque por ejemplohusk $ unhusk $ ("foo", ()) == ("", ()) /= ("foo", ()).Del mismo modo, el ejemplo de un contenedor de tamaño fijo:
de multiplicidad fija 1, no es un aplicativo fuerte, porque si definimos
husk = Leftentonceshusk $ unhusk $ Right () /= Right (), y viceversa. Una forma equivalente de ver esto es que este es solo el aplicativo de escritor para su elección de monoideBool.Así que existen solicitantes de "tamaño fijo" que no lo son
StrongApplicative. Queda por ver si todos losStrongApplicatives son de tamaño fijo.fuente
Tomemos los functores representables como nuestra definición de "contenedor de tamaño fijo":
El real
Representabletiene algunas leyes y superclases, pero a los efectos de esta respuesta, en realidad solo necesitamos dos propiedades:(De acuerdo, también necesitamos una ley respetuosa
instance StrongApplicative ((->) r). Fácil, fácil, ya está de acuerdo en que existe).Si tomamos esa definición, entonces puedo confirmar esa conjetura 1:
es verdad. Así es cómo:
Hay muchas leyes que demostrar, pero me centraré solo en los Cuatro Grandes que se
StrongApplicativesuman: probablemente ya creas en los que están por delanteApplicativeyOpApplicative, pero si no lo haces, sus pruebas se parecen a las siguientes ( que a su vez se parecen mucho). Para mayor claridad, voy a utilizarzipf,huskfetc. para la instancia de la función, yzipr,huskr, etc., para la instancia representable, para que pueda realizar un seguimiento de cuál es cuál. (¡Y para que sea fácil verificar que no tomamos lo que estamos tratando de probar como una suposición! Está bien usarlounhuskf . huskf = idal probarunhuskr . huskr = id, pero sería un error asumirlounhuskr . huskr = iden esa misma prueba).La prueba de cada ley procede básicamente de la misma manera: desenrolla las definiciones, descarta el isomorfismo que
Representablete da, luego usa la ley análoga para las funciones.fuente
instance StrongApplicative f => Representable f where type Rep f = forall x. f x -> x.indexes fácil. Todavía no he resuelto el trucotabulate, pero parece tentadormente cerca.StrongApplicativeinstancia también, pero no pude probar las leyes. ¡Felicitaciones por resolverlo! Traté de hacer laRepresentableinstancia bienStrongApplicative, pero no pude pensar en un buenReptipo. Me gustaría saber, ¿cómoforall x. f x -> xlogras esto?forall x. f x -> xson exactamente aquellas funciones que eligen un agujero y devuelven el valor en ese agujero. (Y, mientras pensaba en cómo implementartabulate, se me ocurrió una objeción al tipo paraunhusk; vea los comentarios sobre la pregunta en sí para más detalles).forall x. f x -> xfuncione comoRep. Mi razonamiento es que, al usar estoRep, puede escribirindexpara cualquier tipo, no solo paraStrongApplicativelos demás, así que sospecho queforall x. f x -> xpodría ser demasiado general.