Al explicarle a alguien qué es un tipo de clase X, lucho por encontrar buenos ejemplos de estructuras de datos que sean exactamente X.
Entonces, solicito ejemplos para:
- Un constructor de tipos que no es un Functor.
- Un constructor de tipos que es un Functor, pero no Aplicativo.
- Un constructor de tipo que es un aplicativo, pero no es una mónada.
- Un constructor de tipo que es una mónada.
Creo que hay muchos ejemplos de Monad en todas partes, pero un buen ejemplo de Monad con alguna relación con ejemplos anteriores podría completar la imagen.
Busco ejemplos que serían similares entre sí, que difieren solo en aspectos importantes para pertenecer a la clase de tipo particular.
Si uno pudiera obtener un ejemplo de Arrow en algún lugar de esta jerarquía (¿es entre Applicative y Monad?), ¡Eso también sería genial!
haskell
monads
functor
applicative
Rotsor
fuente
fuente
* -> *
) para el cual no existe un adecuadofmap
?a -> String
no es un functor.a -> String
es un functor matemático, pero no un HaskellFunctor
, para ser claros.(a -> b) -> f b -> f a
Respuestas:
Un constructor de tipos que no es un Functor:
Puede hacer un functor contravariante, pero no un functor (covariante). Intenta escribir
fmap
y fracasarás. Tenga en cuenta que la versión del fundente contravariante se invierte:Un constructor de tipos que es un functor, pero no Aplicativo:
No tengo un buen ejemplo. Hay
Const
, pero idealmente me gustaría un no monoide concreto y no puedo pensar en ninguno. Todos los tipos son básicamente numéricos, enumeraciones, productos, sumas o funciones cuando se llega al final. Puede ver a continuación el trabajador porcino y no estoy de acuerdo sobre siData.Void
es unMonoid
;Dado que
_|_
es un valor legal en Haskell, y de hecho es el único valor legal deData.Void
este, cumple con las reglas de Monoid. No estoy seguro de quéunsafeCoerce
tiene que ver con esto, porque su programa ya no garantiza que no violará la semántica de Haskell tan pronto como utilice cualquierunsafe
función.Consulte la Wiki de Haskell para obtener un artículo sobre la parte inferior ( enlace ) o las funciones inseguras ( enlace ).
Me pregunto si es posible crear un constructor de este tipo utilizando un sistema de tipos más rico, como Agda o Haskell con varias extensiones.
Un constructor de tipos que es Aplicativo, pero no Mónada:
Puede hacer un aplicativo con algo como:
Pero si lo convierte en una mónada, podría obtener un desajuste de dimensión. Sospecho que ejemplos como este son raros en la práctica.
Un constructor de tipos que es una mónada:
Acerca de las flechas:
Preguntar dónde se encuentra una flecha en esta jerarquía es como preguntar qué tipo de forma es "roja". Tenga en cuenta el tipo de desajuste:
pero,
fuente
Either a
como un ejemplo para el último caso, ya que es más fácil de entender.ZipList
._|_
habita cada tipo en *, pero el puntoVoid
es que deberías inclinarte hacia atrás para construir uno o has destruido su valor. Es por eso que no es una instancia de Enum, Monoid, etc. Si ya tiene uno, me complace dejarlos juntarlos (dándole unSemigroup
) peromempty
, pero no le doy herramientas para construir explícitamente un valor de tipoVoid
envoid
. Tienes que cargar el arma, apuntarla a tu pie y apretar el gatillo tú mismo.Mi estilo puede estar limitado por mi teléfono, pero aquí va.
No puede ser un Functor. Si fuera así, tendríamos
y la luna estaría hecha de queso verde.
mientras tanto
es un functor
pero no puede ser aplicativo, o tendríamos
y Green estaría hecho de queso Moon (que en realidad puede suceder, pero solo más tarde en la noche).
(Nota adicional:
Void
comoData.Void
es un tipo de datos vacío. Si intenta usarloundefined
para demostrar que es un Monoide, lo usaréunsafeCoerce
para demostrar que no lo es).Alegremente
es aplicativo de muchas maneras, por ejemplo, como lo diría Dijkstra,
pero no puede ser una mónada. Para ver por qué no, observe que el retorno debe ser constante
Boo True
oBoo False
, y de ahí queno puede sostenerse.
Oh si, casi me olvido
es una mónada Ruede el suyo.
Avión para atrapar ...
fuente
mempty
._|_
Creo que las otras respuestas omitieron algunos ejemplos simples y comunes:
Un constructor de tipos que es un Functor pero no un Aplicativo. Un ejemplo simple es un par:
Pero no hay forma de definir su
Applicative
instancia sin imponer restricciones adicionalesr
. En particular, no hay forma de definirpure :: a -> (r, a)
un arbitrarior
.Un constructor de tipo que es un aplicativo, pero no es una mónada. Un ejemplo bien conocido es ZipList . (Es una
newtype
envoltura de listas y proporciona unaApplicative
instancia diferente para ellos).fmap
se define de la manera habitual. Peropure
y<*>
se definen comopor lo que
pure
crea una lista infinita repitiendo el valor dado y<*>
cremalleras una lista de funciones con una lista de valores - se aplica i función -ésimo al i -ésimo elemento. (El estándar<*>
en[]
produce todas las combinaciones posibles de aplicar la función i - ésima al elemento j -ésimo). Pero no hay una manera sensata de cómo definir una mónada (ver esta publicación ).¿Cómo encajan las flechas en la jerarquía de fundador / aplicativo / mónada? Ver modismos son ajenos, las flechas son meticulosas, las mónadas son promiscuas por Sam Lindley, Philip Wadler, Jeremy Yallop. MSFP 2008. (Ellos llaman modismos de los ficticios aplicativos ). El resumen:
fuente
((,) r)
es un functor que no es un aplicativo; pero esto es solo porque generalmente no se puede definirpure
para todosr
a la vez. Por lo tanto, es un capricho de la concisión del lenguaje, de tratar de definir una colección (infinita) de functores aplicativos con una definición depure
y<*>
; en este sentido, no parece haber nada matemáticamente profundo en este contraejemplo, ya que, para cualquier concretor
,((,) r)
puede convertirse en un aplicador aplicador. Pregunta: ¿Puedes pensar en un functor de HORMIGÓN que no sea aplicativo?Un buen ejemplo para un constructor de tipos que no es un functor es
Set
: No puede implementarfmap :: (a -> b) -> f a -> f b
, porque sin una restricción adicionalOrd b
no puede construirf b
.fuente
fmap
es solo un problema de idioma / implementación. Además, es posible ajustarseSet
a la mónada de continuación, lo que la convierte en una mónada con todas las propiedades que esperaríamos, vea esta pregunta (aunque no estoy seguro de si se puede hacer de manera eficiente).b
puede ser indecidible, en ese caso no se puede definirfmap
!Functor
instancia con laOrd
restricción, pero podría ser posible con una definición diferenteFunctor
o mejor soporte de idioma. En realidad, con ConstraintKinds es posible definir una clase de tipo que se pueda parametrizar de esta manera.ord
restricción, el hecho de que aSet
no pueda contener entradas duplicadas significa quefmap
podría alterar el contexto. Esto viola la ley de asociatividad.Me gustaría proponer un enfoque más sistemático para responder esta pregunta, y también mostrar ejemplos que no usen ningún truco especial como los valores "inferiores" o los tipos de datos infinitos o algo así.
¿Cuándo los constructores de tipos no tienen instancias de clase de tipo?
En general, hay dos razones por las que un constructor de tipos podría no tener una instancia de una clase de tipo determinada:
Los ejemplos del primer tipo son más fáciles que los del segundo tipo porque para el primer tipo, solo necesitamos verificar si uno puede implementar una función con una firma de tipo dada, mientras que para el segundo tipo, debemos demostrar que no hay implementación posiblemente podría satisfacer las leyes.
Ejemplos específicos
Un constructor de tipos que no puede tener una instancia de functor porque el tipo no se puede implementar:
Este es un contrafunctor, no un functor, con respecto al parámetro tipo
a
, porque estáa
en una posición contravariante. Es imposible implementar una función con firma de tipo(a -> b) -> F z a -> F z b
.Un constructor de tipos que no es un functor legal aunque
fmap
se pueda implementar la firma de tipo de :El aspecto curioso de este ejemplo es que podemos poner en práctica
fmap
del tipo correcto a pesar de queF
no es posible que sea un funtor, ya que utilizaa
en una posición contravariante. Por lo tanto, esta implementación que sefmap
muestra arriba es engañosa, a pesar de que tiene la firma de tipo correcta (creo que esta es la única implementación posible de esa firma de tipo), las leyes de functor no están satisfechas. Por ejemplo,fmap id
≠id
, porquelet (Q(f,_)) = fmap id (Q(read,"123")) in f "456"
es123
, perolet (Q(f,_)) = id (Q(read,"123")) in f "456"
es456
.De hecho,
F
es solo un profunctor, no es ni un functor ni un contrafunctor.Un functor legal que no es aplicativo porque la firma de tipo de
pure
no se puede implementar: tome la mónada Writer(a, w)
y elimine la restricción quew
debería ser un monoide. Entonces es imposible construir un valor de tipo a(a, w)
partir dea
.Un funtor aplicativo que no es porque el tipo de firma
<*>
no pueden ser implementadas:data F a = Either (Int -> a) (String -> a)
.Un functor que no es legalmente aplicativo a pesar de que se pueden implementar los métodos de clase de tipo:
El constructor de tipos
P
es un functor porquea
solo se usa en posiciones covariantes.La única implementación posible de la firma de tipo de
<*>
es una función que siempre devuelveNothing
:Pero esta implementación no satisface la ley de identidad para los actores aplicativos.
Applicative
pero no unMonad
porque la firma de tipo debind
no se puede implementar.¡No conozco ninguno de esos ejemplos!
Applicative
pero noMonad
porque las leyes no se pueden cumplir a pesar de quebind
se puede implementar la firma de tipo .Este ejemplo ha generado bastante discusión, por lo que es seguro decir que demostrar que este ejemplo es correcto no es fácil. Pero varias personas han verificado esto independientemente por diferentes métodos. Ver Es `datos PoE a = Vacío | Par aa` una mónada? para discusión adicional.
Es algo engorroso demostrar que no hay una
Monad
instancia legal . La razón del comportamiento no monádico es que no hay una forma natural de implementarbind
cuándo una funciónf :: a -> B b
podría regresarNothing
oJust
para diferentes valores dea
.Quizás sea más claro considerarlo
Maybe (a, a, a)
, que tampoco es una mónada, y tratar de implementarlojoin
. Uno encontrará que no hay una forma intuitiva razonable de implementaciónjoin
.En los casos indicados por
???
, parece claro que no podemos producirJust (z1, z2, z3)
de manera razonable y simétrica a partir de seis valores diferentes de tipoa
. Ciertamente podríamos elegir algún subconjunto arbitrario de estos seis valores, por ejemplo, siempre tomar el primer no vacíoMaybe
, pero esto no satisfaría las leyes de la mónada. RegresarNothing
tampoco satisfará las leyes.bind
, pero no cumple con las leyes de identidad.La mónada usual en forma de árbol (o "un árbol con ramas en forma de functor") se define como
Esta es una mónada gratis sobre el functor
f
. La forma de los datos es un árbol donde cada punto de ramificación es un "functor-ful" de subárboles. El árbol binario estándar se obtendría contype f a = (a, a)
.Si modificamos esta estructura de datos haciendo también las hojas en forma de functor
f
, obtenemos lo que yo llamo una "semimonada",bind
que cumple con las leyes de naturalidad y asociatividad, pero supure
método falla una de las leyes de identidad. "Los semimonads son semigrupos en la categoría de endofunctores, ¿cuál es el problema?" Esta es la clase de tipoBind
.Por simplicidad, defino el
join
método en lugar debind
:El injerto de rama es estándar, pero el injerto de hoja no es estándar y produce a
Branch
. Esto no es un problema para la ley de asociatividad, pero rompe una de las leyes de identidad.¿Cuándo los tipos polinómicos tienen instancias de mónada?
Ninguno de los funtores
Maybe (a, a)
yMaybe (a, a, a)
se puede dar un legítimoMonad
ejemplo, a pesar de que son obviamenteApplicative
.Estos functores no tienen trucos,
Void
ni enbottom
ningún lado, ni pereza / rigidez engañosas, ni estructuras infinitas, ni restricciones de clase de tipo. LaApplicative
instancia es completamente estándar. Las funcionesreturn
ybind
se pueden implementar para estos functores pero no satisfarán las leyes de la mónada. En otras palabras, estos functores no son mónadas porque falta una estructura específica (pero no es fácil entender qué es exactamente lo que falta). Como ejemplo, un pequeño cambio en el functor puede convertirlo en una mónada:data Maybe a = Nothing | Just a
es una mónada. Otro functor similardata P12 a = Either a (a, a)
es también una mónada.Construcciones para mónadas polinómicas
En general, aquí hay algunas construcciones que producen
Monad
s legales a partir de tipos polinomiales. En todas estas construcciones,M
es una mónada:type M a = Either c (w, a)
dondew
hay un monoidetype M a = m (Either c (w, a))
dondem
hay alguna mónada yw
es cualquier monoidetype M a = (m1 a, m2 a)
dondem1
ym2
son mónadastype M a = Either a (m a)
¿Dóndem
está alguna mónada?La primera construcción es
WriterT w (Either c)
, la segunda construcción esWriterT w (EitherT c m)
. La tercera construcción es un producto de mónadaspure @M
por componentes : se define como el producto por componentes depure @m1
ypure @m2
, yjoin @M
se define omitiendo datos de productos cruzados (por ejemplo,m1 (m1 a, m2 a)
se asignam1 (m1 a)
omitiendo la segunda parte de la tupla):La cuarta construcción se define como
He comprobado que las cuatro construcciones producen mónadas legales.
Yo conjeturo que no hay otras construcciones para mónadas polinómicas. Por ejemplo, el functor
Maybe (Either (a, a) (a, a, a, a))
no se obtiene a través de ninguna de estas construcciones y, por lo tanto, no es monádico. Sin embargo,Either (a, a) (a, a, a)
es monádica porque es isomorfo al producto de tres mónadasa
,a
, yMaybe a
. Además,Either (a,a) (a,a,a,a)
es monádico porque es isomorfo al producto dea
yEither a (a, a, a)
.Las cuatro construcciones mostradas arriba nos permitirán obtener cualquier suma de cualquier número de productos de cualquier número de
a
's, por ejemplo,Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a))
etc. Todos los constructores de tipo tendrán (al menos una)Monad
instancia.Queda por ver, por supuesto, qué casos de uso podrían existir para tales mónadas. Otro problema es que las
Monad
instancias derivadas a través de las construcciones 1-4 en general no son únicas. Por ejemplo, al constructor de tipostype F a = Either a (a, a)
se le puede dar unaMonad
instancia de dos maneras: mediante la construcción 4 con la mónada(a, a)
y mediante la construcción 3 con el isomorfismo de tipoEither a (a, a) = (a, Maybe a)
. Nuevamente, encontrar casos de uso para estas implementaciones no es inmediatamente obvio.Queda una pregunta: dado un tipo de datos polinómico arbitrario, cómo reconocer si tiene una
Monad
instancia. No sé cómo demostrar que no hay otras construcciones para mónadas polinómicas. No creo que exista ninguna teoría hasta ahora para responder a esta pregunta.fuente
B
es una mónada. ¿Puedes dar un contraejemplo a este enlacePair x y >>= f = case (f x, f y) of (Pair x' _,Pair _ y') -> Pair x' y' ; _ -> Empty
?f
tal quef x
esEmpty
perof y
es unPair
, y en el siguiente paso ambos lo sonPair
. Verifiqué a mano que las leyes no son válidas para esta implementación o para cualquier otra implementación. Pero es bastante trabajo hacer eso. ¡Ojalá hubiera una manera más fácil de resolver esto!Maybe
porqueMaybe
no contiene valores diferentes de losa
que preocuparse.Monad
instancia consta de funcionesreturn
ybind
que satisfacen las leyes. Hay dos implementacionesreturn
y 25 implementacionesbind
que se ajustan a los tipos requeridos. Puede mostrar mediante cálculo directo que ninguna de las implementaciones cumple con las leyes. Para reducir la cantidad de trabajo requerido, utilicé enjoin
lugar debind
y usé primero las leyes de identidad. Pero ha sido un buen trabajo.Traversable
sea necesario.m (Either a (m a))
se transforma usandopure @m
enm (Either (m a) (m a))
. Luego trivialmenteEither (m a) (m a) -> m a
, y podemos usarjoin @m
. Esa fue la implementación por la cual revisé las leyes.