haskell - monad - “<-” enlaces en notación do
list monad (6)
Me cuesta mucho entender esto. Al escribir en notación do, ¿en qué se diferencian las siguientes dos líneas?
1. let x = expression
2. x <- expression
No puedo verlo A veces uno funciona, otras veces el otro. Pero rara vez ambos. "Learn you a haskell" dice que <-
une el lado derecho al símbolo de la izquierda. Pero, ¿en qué se diferencia eso de simplemente definir x
con let
?
Aquí hay un ejemplo simple que le muestra la diferencia. Considere las dos expresiones simples siguientes:
letExpression = 2
bindExpression = Just 2
La información que está intentando recuperar es el número 2
. Así es como lo haces:
let x = letExpression
x <- bindExpression
let
poner directamente el valor 2
en x
. <-
extrae el valor 2
del Just
y lo pone en x
.
Puede ver con ese ejemplo, por qué estas dos notaciones no son intercambiables:
let x = bindExpression
pondría directamente el valor Just 2
en x
. x <- letExpression
no tendría nada que extraer y poner en x
.
En la forma let
, la expression
es un valor no monádico, mientras que el lado derecho de a <-
es una expresión monádica. Por ejemplo, solo puede tener una operación de E / S (de tipo IO t
) en el segundo tipo de enlace. En detalle, las dos formas se pueden traducir aproximadamente como (donde ==>
muestra la traducción):
do {let x = expression; rest} ==> let x = expression in do {rest}
y
do {x <- operation; rest} ==> operation >>= (/ x -> do {rest})
En un enlace let
, la expresión puede tener cualquier tipo, y todo lo que está haciendo es darle un nombre (o un patrón que coincida con su estructura interna).
En la versión <-
, la expresión debe tener el tipo ma
, donde m
es la mónada en que se encuentra el bloque do
. Así que en la mónada IO
, por ejemplo, los enlaces de esta forma deben tener algún valor del tipo IO a
en la mano derecha lado. La parte a (dentro del valor monádico) es lo que está vinculado al patrón en el lado izquierdo. Esto le permite extraer los "contenidos" de la mónada dentro del alcance limitado del bloque do
.
La notación de do
es, como puede haber leído, solo azúcar sintáctica sobre los operadores de unión monádica ( >>=
y >>
). x <- expression
de azúcares a expression >>= /x ->
y expression
(por sí misma, sin los <-
) azúcares de expression >>
. Esto solo proporciona una sintaxis más conveniente para definir largas cadenas de cómputos monádicos, que de otra manera tienden a construir una masa bastante impresionante de lambdas anidadas.
Deje let
fijaciones no se deshidraten, en realidad. La única diferencia entre let
un bloque do
y let
fuera de un bloque do
es que la versión do
no requiere la palabra clave in
para seguirlo; los nombres que une están implícitamente en el alcance del resto del bloque do
.
Haskell reconcilia la programación imperativa de efecto colateral con la programación funcional pura al representar acciones imperativas con tipos de forma IO a
: el tipo de acción imperativa que produce un resultado de tipo a
.
Una de las consecuencias de esto es que vincular una variable al valor de una expresión y vincularla al resultado de ejecutar una acción son dos cosas diferentes:
x <- action -- execute action and bind x to the result; may cause effect
let x = expression -- bind x to the value of the expression; no side effects
Por getLine :: IO String
tanto, getLine :: IO String
es una acción, lo que significa que debe usarse de esta manera:
do line <- getLine -- side effect: read from stdin
-- ...do stuff with line
Mientras que line1 ++ line2 :: String
es una expresión pura, y debe usarse con let
:
do line1 <- getLine -- executes an action
line2 <- getLine -- executes an action
let joined = line1 ++ line2 -- pure calculation; no action is executed
return joined
La instrucción <-
extraerá el valor de una mónada, y la declaración let
no lo hará.
import Data.Typeable
readInt :: String -> IO Int
readInt s = do
putStrLn $ "Enter value for " ++ s ++ ": "
readLn
main = do
x <- readInt "x"
let y = readInt "y"
putStrLn $ "x :: " ++ show (typeOf x)
putStrLn $ "y :: " ++ show (typeOf y)
Cuando se ejecuta, el programa le pedirá el valor de x, porque la acción monádica readInt "x"
se ejecuta mediante la instrucción <-
. No pedirá el valor de y, porque se readInt "y"
pero no se ejecuta la acción monádica resultante.
Enter value for x: 123 x :: Int y :: IO Int
Desde x :: Int
, puedes hacer cosas normales con él.
putStrLn $ "x = " ++ show x
putStrLn $ "x * 2 = " ++ show (x * 2)
Desde y :: IO Int
, no puedes pretender que es un Int
normal.
putStrLn $ "y = " ++ show y -- ERROR
putStrLn $ "y * 2 = " ++ show (y * 2) -- ERROR
let
simplemente asignar un nombre a, o un patrón coincide en valores arbitrarios.
Para <-
, primero alejémonos de la (no realmente) misteriosa mónada IO
, pero consideremos las mónadas que tienen la noción de un "contenedor", como una lista o Maybe
. Entonces <-
no hace más que "desempaquetar" los elementos de ese contenedor. La operación opuesta de "devolverlo" es el return
. Considere este código:
add m1 m2 = do
v1 <- m1
v2 <- m2
return (v1 + v2)
"Desempaqueta" los elementos de dos contenedores, suma los valores y los envuelve de nuevo en la misma mónada. Trabaja con listas, tomando todas las combinaciones posibles de elementos:
main = print $ add [1, 2, 3] [40, 50]
--[41,51,42,52,43,53]
De hecho, en el caso de listas, también podría escribir add m1 m2 = [v1 + v2 | v1 <- m1, v2 <- m2]
add m1 m2 = [v1 + v2 | v1 <- m1, v2 <- m2]
. Pero nuestra versión también funciona con Maybe
s:
main = print $ add (Just 3) (Just 12)
--Just 15
main = print $ add (Just 3) Nothing
--Nothing
Ahora IO
no es tan diferente en absoluto. Es un contenedor para un solo valor, pero es un valor impuro "peligroso" como un virus, que no debemos tocar directamente. El do
Block está aquí en nuestra contención de vidrio, y los <-
son los "guantes" incorporados para manipular las cosas que están dentro. Con la return
, entregamos el contenedor completo e intacto (y no solo el contenido peligroso) cuando estamos listos. Por cierto, la función de add
funciona con valores de IO
(que obtuvimos de un archivo o la línea de comandos o un generador aleatorio ...) también.