Question

I'm working through the Write Yourself a Scheme tutorial, and one code block made me wonder about the difference between bind and assignment:

 parseAtom = do first <- letter <|> symbol
            rest <- many (letter <|> digit <|> symbol)
            let atom = first:rest
            return $ case atom of 
                       "#t" -> Bool True
                       "#f" -> Bool False
                       _    -> Atom atom

Why let atom = instead of atom <-? Thus, I tried:

parseAtom = do first <- letter <|> symbol
           rest <- many (letter <|> digit <|> symbol)
           atom <- first : rest
           return $ case atom of
                "#t" -> Bool True
                "#f" -> Bool False
                _ -> Atom atom

And I get the compile error:

    Couldn't match expected type `[Char]'
       against inferred type `Text.Parsec.Prim.ParsecT
                                String () Data.Functor.Identity.Identity Char'
In a stmt of a 'do' expression: atom <- first : rest

I can't figure precisely what this means, which is probably due to an imprecise understanding of do or monads. (I've read Learn You a Haskell along with various other monad/do tutorials, and other SO questions note that indentation oftens causes issues here, but I think I'm indenting correctly)

Was it helpful?

Solution

You're inside the parser monad, so the right side of <- needs to be a parser expression. However first : rest is simply a list (a string, specifically), not a parser expression.

What v <- someParser does is it applies the given parser to the input and then stores the matched text in v. A string is not a parser and it can't be applied to the input and there would be no matched text to store in v. So all that you could do is to store the string in v, which you'd do by writing let v = someString.

OTHER TIPS

You're dealing with two syntactic sugar constructs: a do-notation and a let block inside a do-notation. Things should become clear if we simply desugar your correct function implementation.

Original

parseAtom = do 
  first <- letter <|> symbol
  rest <- many (letter <|> digit <|> symbol)
  let atom = first:rest
  return $ case atom of 
    "#t" -> Bool True
    "#f" -> Bool False
    _    -> Atom atom

Let-block desugared

parseAtom = do 
  first <- letter <|> symbol
  rest <- many (letter <|> digit <|> symbol)
  let 
    atom = first : rest
    in do
      return $ case atom of 
        "#t" -> Bool True
        "#f" -> Bool False
        _    -> Atom atom

Desugared completely

parseAtom =
  (letter <|> symbol) >>= \first ->
  many (letter <|> digit <|> symbol) >>= \rest ->
  let 
    atom = first : rest
    in return $ case atom of 
      "#t" -> Bool True
      "#f" -> Bool False
      _    -> Atom atom

It should also be noted that a simple let-expression inside a do-notation can be replaced with a bind-expression as you expected - all you need to do is just drop in a return. So let atom = first:rest can be translated to atom <- return $ first : rest.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top