haskell - monadas - ¿Por qué<$> y<*> toman entrada en un orden opuesto a>>=?
monads haskell (2)
Entiendo el razonamiento detrás de la firma de tipo <$>
, ya que es solo una versión infijo de fmap
, pero comparándolo con la firma de tipo >>=
''s tiene mucho menos sentido para mí.
Primero establezcamos lo que quiero decir con eso.
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(<$>) :: Functor f => (a -> b) -> f a -> f b
Si observamos las firmas de tipo, podemos ver que >>=
toma un valor a la izquierda y una función a la derecha, lo cual tiene mucho sentido si consideramos su propiedad de encadenamiento: foo >>= bar >>= baz
Lo que me lleva a preguntarme, ¿por qué no <*>
y <$>
hacen eso también? no puede escribir foo <*> bar <*> baz
porque requeriría que la salida de foo <*> bar
sea una función, no un valor.
Sé que <**>
y =<<
existen que ambos cambian el orden de los parámetros, lo que me permite hacer algo como:
Just 4 <**> pure (+3) <**> pure (*2) >>= (/x -> Just (x-3))
Que podría haber sido bellamente reducido a:
Just 4 <$$> (+3) <$$> (*2) >>= (/x -> Just (x-3))
Si <$$>
ha existido, o si el orden de los parámetros de <$>
y <*>
se ha invertido.
Otra cosa que me hace preguntarme por qué existe la diferencia es que dificulta que los recién llegados se acostumbren y / o recuerden si es la función o el valor que viene primero, sin tener que buscarla.
Entonces, ¿por qué es que en los casos de <*>
y <$>
es fn op val
pero con >>=
it val op fn
?
La respuesta a "por qué toma parámetros en este orden" es básicamente "porque". Quien definió estas funciones pensó que era la mejor manera. (Y probablemente no fue la misma persona en cada caso). Sin embargo, ofreceré algunos ejemplos:
Supongamos que tenemos una mónada de análisis de algún tipo. Supongamos también que hemos definido
data Foobar = Foobar X Y Z
parseFoo :: Parser X
parseBar :: Parser Y
parseBaz :: Parser Z
Entonces podemos escribir
parseFoobar :: Parser Foobar
parseFoobar = do
foo <- parseFoo
bar <- parseBar
baz <- parseBaz
return (Foobar foo bar baz)
O, explícitamente,
parseFoobar =
parseFoo >>= / foo ->
parseBar >>= / bar ->
parseBaz >>= / baz ->
return (Foobar foo bar baz)
Ahora escribamos ese estilo aplicativo:
parseFoobar = return Foobar <*> parseFoo <*> parseBar <*> parseBaz
o alternativamente,
parseFoobar = Foobar <$> parseFoo <*> parseBar <*> parseBaz
Si suponemos que <**> = flip <*>
existe (y tiene la asociatividad correcta), entonces tenemos
parseFoobar = parseBaz <**> parseBar <**> parseFoo <**> return Foobar
Eso se ve extraño. ¿Con la función al final, y los argumentos en orden inverso? ¿Por qué querrías escribirlo de esa manera? (Tenga en cuenta que los efectos también están en orden inverso).
En la versión monádica, los efectos ocurren de arriba a abajo. En la versión aplicativa, los efectos ocurren de izquierda a derecha. Eso parece "natural".
No dejes que las mónadas se interpongan aquí. Piensa en la aplicación.
Comparar:
(<$>) :: Functor f => (a -> b) -> f a -> f b
y
($) :: (a -> b) -> a -> b
Puede ver que hay una conexión entre la aplicación normal y la aplicación bajo un funtor.
Trivia: ha habido proposals para usar corchetes para sobrecargar espacios en blanco (aplicación) para que podamos escribir:
(| f a1 .. an |)
en lugar de
pure f <*> a1 <*> .. <*> an