La Applicative
clase 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; OpApplicative
No 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 Applicative
functor (realmente, cualquiera Functor
) es trivial OpApplicative
, no necesariamente hay una buena relación entre las Applicative
laxitudes y las OpApplicative
oplaxidades. 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 Applicative
son una instancia legal de esta clase.
Aquí hay algunos Applicative
functores para los cuales podemos declarar instancias legales de StrongApplicative
:
Identity
VoidF
(->) r
(ver respuestas)Monoid m => (,) m
Vec (n :: Nat)
Stream
(infinito)
Y aquí hay algunos Applicative
s para los que no podemos:
[]
Either e
Maybe
NonEmptyList
El patrón aquí sugiere que la StrongApplicative
clase es en cierto sentido la FixedSize
clase, donde "tamaño fijo" * significa que la multiplicidad ** de habitantes de a
un habitante de f a
es fija.
Esto se puede expresar como dos conjeturas:
- Cada
Applicative
representación de un contenedor de "tamaño fijo" de elementos de su argumento tipo es una instancia deStrongApplicative
- No
StrongApplicative
existe ninguna instancia en la que el número de ocurrencias dea
pueda 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. StrongApplicative
Es 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 StrongApplicative
instancia 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
(->) r
son 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 Representable
clase de tipo en Haskell tiene una tabulate . return = return
ley misteriosa (que realmente ni siquiera tiene sentido para los functores no monádicos), pero nos da 1/4 de las condiciones que necesitamos decir tabulate
y zip
son morfismos de una categoría adecuada de monoides. . Las otras 3 son leyes adicionales que debes exigir.
tabulate
y index
son morfismos de una categoría adecuada ..."
return
no es un problema grave. cotraverse getConst . Const
es una implementación predeterminada para return
/ pure
en términos de Distributive
, y, dado que los distributivos / representables tienen una forma fija, esa implementación es única.