I have a hard time grasping this. When writing in do notation, how are the following two lines different?
1. let x = expression
2. x <- expression
I can't see it. Sometimes one works, some times the other. But rarely both. "Learn you a haskell" says that <-
binds the right side to the symbol on the left. But how is that different from simply defining x
with let
?
Essentially, a >> b can be read like "do a then do b , and return the result of b ". It's similar to the more common bind operator >>= .
As a syntactical convenience, do notation does not add anything essential, but it is often preferable for clarity and style. However, do is not needed for a single action, at all. The Haskell "Hello world" is simply: main = putStrLn "Hello world!"
What is a Monad? A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.
The <-
statement will extract the value from a monad, and the let
statement will not.
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)
When run, the program will ask for the value of x, because the monadic action readInt "x"
is executed by the <-
statement. It will not ask for the value of y, because readInt "y"
is evaluated but the resulting monadic action is not executed.
Enter value for x: 123 x :: Int y :: IO Int
Since x :: Int
, you can do normal Int
things with it.
putStrLn $ "x = " ++ show x
putStrLn $ "x * 2 = " ++ show (x * 2)
Since y :: IO Int
, you can't pretend that it's a regular Int
.
putStrLn $ "y = " ++ show y -- ERROR
putStrLn $ "y * 2 = " ++ show (y * 2) -- ERROR
In a let
binding, the expression can have any type, and all you're doing is giving it a name (or pattern matching on its internal structure).
In the <-
version, the expression must have type m a
, where m
is whatever monad the do
block is in. So in the IO
monad, for instance, bindings of this form must have some value of type IO a
on the right-hand side. The a
part (inside the monadic value) is what is bound to the pattern on the left-hand side. This lets you extract the "contents" of the monad within the limited scope of the do
block.
The do
notation is, as you may have read, just syntactic sugar over the monadic binding operators (>>=
and >>
). x <- expression
de-sugars to expression >>= \x ->
and expression
(by itself, without the <-
) de-sugars to expression >>
. This just gives a more convenient syntax for defining long chains of monadic computations, which otherwise tend to build up a rather impressive mass of nested lambdas.
let
bindings don't de-sugar at all, really. The only difference between let
in a do
block and let
outside of a do
block is that the do
version doesn't require the in
keyword to follow it; the names it binds are implicitly in scope for the rest of the do
block.
In the let
form, the expression
is a non-monadic value, while the right side of a <-
is a monadic expression. For example, you can only have an I/O operation (of type IO t
) in the second kind of binding. In detail, the two forms can be roughly translated as (where ==>
shows the translation):
do {let x = expression; rest} ==> let x = expression in do {rest}
and
do {x <- operation; rest} ==> operation >>= (\ x -> do {rest})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With