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
fuente
(->) 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, laRepresentable
clase de tipo en Haskell tiene unatabulate . 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 decirtabulate
yzip
son morfismos de una categoría adecuada de monoides. . Las otras 3 son leyes adicionales que debes exigir.tabulate
yindex
son morfismos de una categoría adecuada ..."return
no es un problema grave.cotraverse getConst . Const
es una implementación predeterminada parareturn
/pure
en 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
StrongApplicative
leyhusk . 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 ,f
debe 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 == id
implica quef
debe ser de forma fija.fuente
f ()
" implica "que el número de ocurrencias dea
no puede variar" en presencia de GADT de fantasía y esas cosas?a
no puede variar" no es una condición suficiente para unaStrongApplicative
instancia; 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 dea
no 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 dea
s en su interior. (No es un contraejemplo completo aquí porqueFunctor
es difícil, pero ¿cómo sabemos que no se puede arreglar?)Weird ()
es un singleton pero no tiene una forma fija. PeroWeird
no es unFunctor
, así que no puede ser un deStrongApplicative
todos modos. Supongo que la conjetura relevante sería: sif
es aFunctor
, ¿f ()
ser un singleton implica quef
tiene 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
StrongApplicative
en la pregunta original es incorrecto. El escritor aplicativoMonoid => (,) m
no 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 = Left
entonceshusk $ 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 losStrongApplicative
s son de tamaño fijo.fuente
Tomemos los functores representables como nuestra definición de "contenedor de tamaño fijo":
El real
Representable
tiene 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
StrongApplicative
suman: probablemente ya creas en los que están por delanteApplicative
yOpApplicative
, 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
,huskf
etc. 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 = id
al probarunhuskr . huskr = id
, pero sería un error asumirlounhuskr . huskr = id
en esa misma prueba).La prueba de cada ley procede básicamente de la misma manera: desenrolla las definiciones, descarta el isomorfismo que
Representable
te 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
.index
es fácil. Todavía no he resuelto el trucotabulate
, pero parece tentadormente cerca.StrongApplicative
instancia también, pero no pude probar las leyes. ¡Felicitaciones por resolverlo! Traté de hacer laRepresentable
instancia bienStrongApplicative
, pero no pude pensar en un buenRep
tipo. Me gustaría saber, ¿cómoforall x. f x -> x
logras esto?forall x. f x -> x
son 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 -> x
funcione comoRep
. Mi razonamiento es que, al usar estoRep
, puede escribirindex
para cualquier tipo, no solo paraStrongApplicative
los demás, así que sospecho queforall x. f x -> x
podría ser demasiado general.