you monadas monad learn functores example haskell monads functor applicative

haskell - monadas - ¿Es mejor definir Functor en términos de Aplicativo en términos de Mónada, o viceversa?



monad haskell example (5)

A menudo elijo un enfoque inverso en comparación con el de la respuesta de Abrahamson. Defino manualmente solo la instancia de Monad y defino el Applicative y el Functor en términos de ello con la ayuda de funciones ya definidas en Control.Monad , que hace que esas instancias sean iguales para absolutamente cualquier mónada, es decir:

instance Applicative SomeMonad where pure = return (<*>) = ap instance Functore SomeMonad where fmap = liftM

Si bien de esta manera, la definición de Functor y Applicative es siempre "sin cerebro" y muy fácil de razonar, debo señalar que esta no es la solución definitiva, ya que hay casos en los que las instancias se pueden implementar de manera más eficiente o incluso proporcionar nuevas características. Por ejemplo, la instancia Applicative de Concurrently ejecuta cosas ... al mismo tiempo, mientras que la instancia de Monad solo puede ejecutarlas secuencialmente debido a las mónadas naturales.

Esta es una pregunta general, no vinculada a ninguna pieza de código.

Digamos que tienes un tipo T a que se le puede dar una instancia de Monad . Dado que cada mónada es un Applicative asignando pure = return y (<*>) = ap , y luego cada aplicativo es un Functor través de fmap fx = pure f <*> x , ¿es mejor definir primero su instancia de Monad , y Entonces, trivialmente, da T instancias de Applicative y Functor ?

Se siente un poco hacia atrás para mí. Si estuviera haciendo matemáticas en lugar de programar, creo que primero mostraría que mi objeto es un functor, y luego continuaré agregando restricciones hasta que también muestre que es una mónada. Sé que Haskell está meramente inspirado en la teoría de categorías y obviamente las técnicas que uno usaría cuando construyera una prueba no son las técnicas que usaría al escribir un programa útil, pero me gustaría obtener una opinión de la comunidad de Haskell. ¿Es mejor ir de Monad a Functor ? ¿O desde Functor hasta Monad ?


Creo que entiendes mal cómo funcionan las subclases en Haskell. ¡No son como las subclases OO! En cambio, una restricción de subclase, como

class Applicative m => Monad m

dice "cualquier tipo con una estructura de Monad canónica también debe tener una estructura de Applicative canónica". Hay dos razones básicas por las que colocarías una restricción como esa:

  • La estructura de subclase induce una estructura de superclase .
  • La estructura de superclase es un subconjunto natural de la estructura de subclase.

Por ejemplo, considere:

class Vector v where (.^) :: Double -> v -> v (+^) :: v -> v -> v negateV :: v -> v class Metric a where distance :: a -> a -> Double class (Vector v, Metric v) => Norm v where norm :: v -> Double

La primera restricción de Norm en Norm surge porque el concepto de un espacio normado es realmente débil a menos que también asumas una estructura de espacio vectorial; el segundo surge porque (dado un espacio vectorial) una Norm induce una Metric , que puede probar observando que

instance Metric V where distance v0 v1 = norm (v0 .^ negateV v1)

es una instancia de Metric válida para cualquier V con una instancia de Vector válida y una función de norm válida. Decimos que la norma induce una métrica. Ver http://en.wikipedia.org/wiki/Normed_vector_space#Topological_structure .

Las Functor y Applicative en Monad son como Metric , no como Vector : el return y >>= funciones de Monad inducen las estructuras de Functor y Applicative :

  • fmap : se puede definir como fmap fa = a >>= return . f fmap fa = a >>= return . f , que fue liftM en la biblioteca estándar Haskell 98.
  • pure : es la misma operación que return ; los dos nombres son un legado de cuando Applicative no era una súper clase de Monad .
  • <*> : se puede definir como af <*> ax = af >>= / f -> ax >>= / x -> return (fx) , que fue liftM2 ($) en la biblioteca estándar de Haskell 98.
  • join : se puede definir como join aa = aa >>= id .

Así que es perfectamente sensible, matemáticamente, definir las operaciones de Functor y Applicative en términos de Monad .


La magia aquí, que el Haskell usa la notación Kleisli-tiplet de una mónada, es una forma más conveniente, si alguien quiere usar mónadas en la imperativa programación como herramientas.

Hice la misma pregunta, y la respuesta aparece después de un tiempo, si ve las definiciones de Functor , Applicative , Monad en haskell, se pierde un enlace, que es la definición original de la mónada, que contiene solo la operación de unión , que Se puede encontrar en el HaskellWiki .

Con este punto de vista, verá cómo se construyen las mónadas de haskell, el funtor, los funtores aplicativos, las mónadas y el triplete de Kliesli.

Puede encontrar una explicación aproximada aquí: https://github.com/andorp/pearls/blob/master/Monad.hs Y otra con las mismas ideas aquí: http://people.inf.elte.hu/pgj/haskell2/jegyzet/08/Monad.hs


Tiendo a escribir y ver escrito primero la instancia de Functor . Doblemente es así porque si utiliza el pragma LANGUAGE DeriveFunctor , los data Foo a = Foo a deriving ( Functor ) funcionan la mayor parte del tiempo.

Los trucos difíciles están relacionados con el acuerdo de los casos en que su Applicative puede ser más general que su Monad . Por ejemplo, aquí hay un tipo de datos Err

data Err e a = Err [e] | Ok a deriving ( Functor ) instance Applicative (Err e) where pure = Ok Err es <*> Err es'' = Err (es ++ es'') Err es <*> _ = Err es _ <*> Err es = Err es Ok f <*> Ok x = Ok (f x) instance Monad (Err e) where return = pure Err es >>= _ = Err es Ok a >>= f = f a

Anteriormente, definí las instancias en el orden Functor to- Monad y, en forma aislada, cada instancia es correcta. Desafortunadamente, las instancias de Applicative y Monad no se alinean: ap y (<*>) son visiblemente diferentes a como son (>>) y (*>) .

Err "hi" <*> Err "bye" == Err "hibye" Err "hi" `ap` Err "bye" == Err "hi"

Para propósitos de sensibilidad, especialmente una vez que la Propuesta de Solicitud / Mónada esté en manos de todos, estos deben alinearse. Si definió la instance Applicative (Err e) where { pure = return; (<*>) = ap } instance Applicative (Err e) where { pure = return; (<*>) = ap } entonces se alinearán.

Pero luego, finalmente, puede ser capaz de separar cuidadosamente las diferencias entre el Applicative y la Monad para que se comporten de manera diferente en formas benignas, como tener una instancia del Aplicativo más perezosa o más eficiente. En realidad, esto ocurre con bastante frecuencia y creo que el jurado todavía está un poco fuera de lo que significa "benigno" y en qué tipo de "observación" deben alinearse sus instancias. Quizás parte del uso más gregario de esto está en el proyecto Haxl en Facebook, donde la instancia de Applicative está más paralelizada que la de Monad , y por lo tanto es mucho más eficiente a costa de algunos efectos secundarios "no observados" bastante graves.

En cualquier caso, si difieren, documentarlo.


Functor instancias de Functor suelen ser muy simples de definir, normalmente las hago a mano.

Para el Applicative y la Monad , depende. pure y de return suelen ser igualmente fáciles, y realmente no importa en qué clase se ubique la definición expandida. Para el enlace, a veces es benéfico ir a la "forma de categoría", es decir, definir una join'' :: (M (M x)) -> M x especializada join'' :: (M (M x)) -> M x primero y luego a>>=b = join'' $ fmap ba (que por supuesto no funcionaría si hubiera definido fmap en términos de >>= ). Entonces probablemente sea útil simplemente reutilizar (>>=) para la instancia del Applicative .

Otras veces, la instancia de Applicative puede escribirse con bastante facilidad o es más eficiente que la implementación genérica derivada de Monad . En ese caso, definitivamente debe definir <*> separado.