I don't understand when do I have to use let
and when do I have to use the <-
binding.

- 34,979
- 10
- 68
- 73

- 199
- 1
- 1
- 10
-
Read more about what actually `let` means, and what actually `<-` does. And, probably, about the `do` notaion. – lisyarus Feb 29 '16 at 15:15
3 Answers
let
gives a name to the result of a function call.
<-
binds the result of a monadic operation in the current monad to a name.
They're quite different. Use let
for the results of functions outside the monad, i.e. normal pure functions. Use <-
for anything monadic, as it "unwraps" the monad result and lets you get at the value inside it.
For example:
assume an IO function with the following signature
frobnicate :: String -> IO Bool
and a pure function
dothing :: Bool -> Bool
we can do this
main :: IO ()
main = do
x <- frobnicate "Hello"
let y = frobnicate "Hello"
-- z <- dothing x
let z = dothing x
return ()
And we know that x :: Bool
because the Bool
has been extracted from the IO
operation result for us (the operation runs, and the result is called x
so we can use it later).
We also know that y :: IO Bool
- the operation hasn't been run, it's the potential for an IO operation in the future. So the only useful thing we can do with y
is run it later, bind the result and get at the Bool
inside that way, but after a let
that Bool
doesn't even exist yet.
The third line is commented out because it won't compile - you can't do a monadic bind on an operation which is not in the relevant monad. dothing
doesn't return an IO
anything, so you can't bind it inside an IO ()
function.
The fourth line is straightforward - z
is made to be the result of dothing x
, where x
was the value unwrapped from running frobnicate "Hello"
earlier.
All of this is just syntactic sugar for the 'real' monad operations underneath, so that expands (without the commented out part) to something like
main = frobnicate "Hello" >>= (\x -> let y = frobnicate "Hello"
z = dothing x
in return ())
The example of course makes no sense at all, but hopefully it illustrates where let
and <-
differ within do
notation.
TL;DR: use <-
for giving names to the results of monadic operations, let
for giving names to everything else.

- 9,809
- 3
- 27
- 36
<-
is to >>=
(bind
) where let
is to fmap
in a do
block.
Stealing an example from here:
do x1 <- action1 x0
x2 <- action2 x1
action3 x1 x2
-- is equivalent to:
action1 x0 >>= \ x1 -> action2 x1 >>= \ x2 -> action3 x1 x2
action1
, action2
& action3
all return some sort of monad, say:
action1 :: (Monad m) => a -> m b
action2 :: (Monad m) => b -> m c
action3 :: (Monad m) => b -> c -> m d
You can rewrite let bindings as such:
-- assume action1 & action3 are the same
-- but action2 is thus:
action2 :: b -> c
do
x1 <- action1 x0
let x2 = action2 x1
action3 x1 x2
do
x1 <- action1 x0
x2 <- return & action2 x1
action3 x1 x2
-- of course it doesn't make sense to call action2
-- an action anymore, it's just a pure function.

- 15,777
- 9
- 59
- 98
A good example to visualize what <-
does:
do
a <- ['a'..'z']
b <- [1..3]
pure (a,b)
You can try this in the online REPL at try.frege-lang.org (You can enter this as a single line:
do { a <- ['a'..'z']; b <- [1..3]; pure (a,b) }

- 13,126
- 4
- 30
- 52

- 36,037
- 5
- 53
- 100