0

What's the difference in using let or do when trying to work with monadic values? (not sure if this is the right way to phrase it)

For example:

--import System.Random

*Main> dupRand = let i = randomRIO (1, 10) in sequence [i, i]
*Main> :t dupRand
dupRand :: (Random a, Num a) => IO [a]
*Main> dupRand
[8,3]
*Main> dupRand
[2,9]
*Main> dupRand
[9,5]

*Main> dupRand2 = do i <- randomRIO (1, 10); pure [i, i]
*Main> :t dupRand2
dupRand2 :: (Random a, Num a) => IO [a]
*Main> dupRand2
[6,6]
*Main> dupRand2
[9,9]
*Main> dupRand2
[5,5]

why is it that in dupRand2, the function successfully duplicates a random value, whereas in dupRand, the function just seems like it generates two random values?

zli
  • 307
  • 1
  • 12
  • 2
    `dupRand2` doesn't typecheck as written. Maybe you meant `pure [i, i]`? – dfeuer Mar 29 '19 at 21:15
  • 2
    `dupRand2` throws errors for me too. `dupRand` gives different random values because you're saying, consider the action that generates a random int, now consider the list that contains that action twice, now run the actions in that list sequentially left-to-right. – moonGoose Mar 29 '19 at 21:16
  • 3
    leaving aside the fact that `dupRand2` doesn't type-check, the essential difference here is that `randomRIO (1,10)` is an "IO action" that generates a number, this isn't guaranteed to be the same number every time. Whereas `i <- randomRIO (1,10)` "runs" the action and collects its result in the variable `i`, which means that when you later put 2 `i`s in a list they will always have the same value. It's a bit like the difference in an imperative language between calling your random-number-generating function twice, and calling it just once and printing the result twice. – Robin Zigmond Mar 29 '19 at 21:21
  • oops thanks, I did mean `pure` rather than `sequence` in `dupRand2` – zli Mar 29 '19 at 21:28
  • I think this is a nearly exact duplicate of the question I linked in my close-vote. If you disagree, feel free to point out the difference and I'll vote to reopen. – chi Mar 29 '19 at 21:37
  • @chi huh, I've read that question before but somehow I asked this question anyway. i think the questions are very similar, but for people just learning haskell, reading the former question might not lead to a complete understanding of its implications (which resulted in this question). all that being said i'm fine with closing this and marking it as a duplicate – zli Mar 29 '19 at 21:44

1 Answers1

4

In

dupRand =
  let i = randomRIO (1, 10)
  in sequence [i, i]

you bind i to an IO action that, when run, will produce a random number. You make a list of two elements, each of which contains that action. Now

sequence
  :: (Traversable t, Monad m)
  => t (m a) -> m (t a)

In this context, the Traversable is [] and the Monad is IO, so

sequence :: [IO a] -> IO [a]

What this does is run each of the IO actions in a list, returning a list of their results in order. So when we run sequence [i, i], we get a random number, get a random number, and then produce a list with both of them.

dupRand2 doesn't typecheck. I'm guessing you meant

dupRand2 = do
  i <- randomRIO (1, 10)
  pure [i, i]

This is syntactic sugar for

dupRand2 =
  randomRIO (1, 10) >>= \i ->
    pure [i, i]

When run, this action binds i to a random number (rather than an action producing one) and then returns a list with i repeated twice.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • got it thanks. I guess I still need to get out of my imperative mindset where `=` is like `bind` in haskell. is this only an artifact of trying to bind `i` to a random value? i.e. does this mean that in cases where `let i =` or `i <-` to a pure value, the two `dup` functions would be the same? – zli Mar 29 '19 at 21:34
  • 1
    @zli, `let x = e` is almost the same as `x <- pure e` within a `do` block. But `let` and `<-` generally serve different purposes. – dfeuer Mar 29 '19 at 21:36
  • 2
    (Why "almost the same" and not "the same"? Two reasons: `let` can make polymorphic bindings, whereas `<-` is monomorphic; and `let` can make recursive bindings, whereas by default `x <- f x` will either be a scope error or merely shadow the existing `x` with a new one.) – Daniel Wagner Mar 29 '19 at 23:05
  • @DanielWagner, pattern match failure also works differently. – dfeuer Mar 30 '19 at 01:25
  • Oh, was `x` intended to stand in for any pattern rather than a variable specifically? I assumed you would use a different metavariable if you wanted to talk about patterns in general. Anyway, yes, of course for refutable patterns there is another difference. – Daniel Wagner Mar 30 '19 at 01:27
  • @DanielWagner, I thought about calling it `p` to suggest that, but since my main point was that `let` and `<-` are mostly for different things anyway, I figured I'd let it go. – dfeuer Mar 30 '19 at 01:29