Search code examples
haskellcompiler-errorsmonadstype-mismatchdo-notation

What is the difference between `let .. in do` and `<-` notation in Haskell Monads?


I'm trying to implement a function which converts a string to a list of Maybe Ints, e.g. readInts "1 2 42 foo" = [Just 1,Just 2,Just 42,Nothing].

My first aproach was:

readInts (s::String) = do {
    ws <- words s;
    return (map (readMaybe::(String -> Maybe Int)) ws)
}

This resulted in the following error:

lab_monad.hs:20:52:
    Couldn't match type ‘Char’ with ‘[Char]’
    Expected type: [String]
      Actual type: String
    In the second argument of ‘map’, namely ‘ws’
    In the first argument of ‘return’, namely
      ‘(map (readMaybe :: String -> Maybe Int) ws)’
Failed, modules loaded: none.

What I tried next (and worked), was:

readInts (s::String) = do {
    let ws = (words s) in do
        return (map (readMaybe::(String -> Maybe Int)) ws)
} 

My question here is, words s obviously is of type [String]. Why does the interpreter say it is a String? What am I not understanding about <- operator?


Solution

  • ws <- words s, in the list monad, nondeterministically assigns one word from words s to ws; the remaining code simply works with that one word, and the return function "magically" combines the results of working on all the words into the result list.

    readInts s = do
       ws <- words s  -- ws represents *each* word in words s
       return (readMaybe ws)
    

    The do notation is just syntactic sugar for using monadic bind:

    readInts s = words s >>= (\ws -> return (readMaybe ws))
    

    Without using the Monad instance for lists, you can use map to apply the same function to each word.

    readInts s = map readMaybe (words s)
    

    let, on the other hand, simply provides a name for a more complicated expression to be used in another expression. It can be considered syntactic sugar for defining and immediately applying an anonymous function. That is,

    let x = y + z in f x
    

    is equivalent to

    (\x -> f x) (y + z)
      ^     ^      ^
      |     |      |
      |     |      RHS of let binding
      |     part after "in"
      LHS of let binding
    

    A let statement with multiple bindings is equivalent to nested let statements:

    let x = y + z
        a = b + c
    in x + a
    

    is equivalent to

    let x = y + z
    in let a = b + c
       in x + a
    

    which desugars to

    (\x -> (\a -> x + a)(b + c))(y + z)