2

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)

hammar
  • 138,522
  • 17
  • 304
  • 385
Cannoliopsida
  • 3,044
  • 5
  • 36
  • 61

2 Answers2

5

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.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • I think this is becoming clear. So if I want to see how <-, >>, or >>= are defined for a particular monad, how do I do that? Am I looking in the right spot for function definitions on http://hackage.haskell.org/package/parsec ? – Cannoliopsida Jun 09 '13 at 16:50
  • 1
    @Akroy If you want to know the exact definition, you'd read the source (though that would not necessarily be very helpful as the definition could be quite complicated). If you just want to know the behaviour, reading the documentation may suffice. And yes, you're looking in the right spot for Parsec: from that page you'd go to `Text.Parsec.Prim` where you'd find the docs for `ParsecT`. Clicking on the "source" link will get you to the definition and scrolling down some will get you to where `Monad` instance is defined (scrolling down some more will get you to the definitions of the... – sepp2k Jun 09 '13 at 17:00
  • 2
    ... relevant functions `parserReturn` and `parserBind`). However, as I said, those definitions aren't really easy to digest. PS: `<-` is defined directly by the language in terms of `>>=`. And `x >> y` will be equivalent to `x >>= \_ -> y`. So you just need to care about `>>=`. – sepp2k Jun 09 '13 at 17:02
  • Awesome, thanks so much! Both parser and 'do' are more clear now. – Cannoliopsida Jun 09 '13 at 17:04
5

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.

Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
  • 3
    It works here, but in general you cannot desugar `let` statements to `... <- return ...`, as there are several differences (recursive vs. non-recursive, irrefutable pattern match vs. calls `fail`, generalized type vs. not generalized). The correct desugaring of a `let` statement is a `let ... in` expression. – hammar Jun 09 '13 at 18:05
  • @hammar You're absolutely right. Thanks! I've corrected the answer. – Nikita Volkov Jun 09 '13 at 18:37