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