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
(->) 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, la Representableclase de tipo en Haskell tiene una tabulate . 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 decir tabulatey zipson morfismos de una categoría adecuada de monoides. . Las otras 3 son leyes adicionales que debes exigir.
tabulatey indexson morfismos de una categoría adecuada ..."
returnno es un problema grave. cotraverse getConst . Constes una implementación predeterminada para return/ pureen términos de Distributive, y, dado que los distributivos / representables tienen una forma fija, esa implementación es única.