3

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

Vincent Savard
  • 34,979
  • 10
  • 68
  • 73
E.F
  • 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 Answers3

7

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.

Matthew Walton
  • 9,809
  • 3
  • 27
  • 36
3

<- 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.
muhuk
  • 15,777
  • 9
  • 59
  • 98
1

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) }
Marimuthu Madasamy
  • 13,126
  • 4
  • 30
  • 52
Ingo
  • 36,037
  • 5
  • 53
  • 100