4

I'm trying to figure out how to build a "purely applicative parser" based on a simple parser implementation. The parser would not use monads in its implementation. I asked this question previously but mis-framed it so I'm trying again.

Here is the basic type and its Functor, Applicative and Alternative implementations:

newtype Parser a = Parser { parse :: String -> [(a,String)] }

instance Functor Parser where
  fmap f (Parser cs) = Parser (\s -> [(f a, b) | (a, b) <- cs s])

instance Applicative Parser where
  pure = Parser (\s -> [(a,s)])
  (Parser cs1) <*> (Parser cs2) = Parser (\s -> [(f a, s2) | (f, s1) <- cs1 s, (a, s2) <- cs2 s1])

instance Alternative Parser where
  empty = Parser $ \s -> []
  p <|> q = Parser $ \s ->
    case parse p s of
      [] -> parse q s
      r  -> r

The item function takes a character off the stream:

item :: Parser Char
item = Parser $ \s ->
  case s of
   [] -> []
   (c:cs) -> [(c,cs)]

At this point, I want to implement digit. I can of course do this:

digit = Parser $ \s ->
  case s of
    [] -> []
    (c:cs) -> if isDigit c then [(c, cs)] else []

but I'm replicating the code of item. I'd like to implement digit based on item.

How do I go about implementing digit, using item to take a character off the stream and then checking to see if the character is a digit without bringing monadic concepts into the implementation?

  • 1
    I'd say this seems to require monads. You can workaround the problem by generalizing digit to arbitrary predicates e.g. `sat :: (Char->Bool) -> Parser Char`. I'm pretty sure you were not asking for that, though... :-) – chi Feb 25 '16 at 22:57
  • @chi Thanks. Can you explain why this requires monads? I'd like to learn how to identify whether it needs to use monads or not. –  Feb 25 '16 at 22:58
  • Even better for a purely applicative parser (actually alternative): `newtype Parser a = Parser {run :: forall f. Alternative f => (Char -> f a) -> f a}` (you can do it with `Applicative`, but `Alternative` allows choice). – PyRulez Feb 25 '16 at 23:03
  • @PyRulez Can you elaborate? I don't see how that implementation would look. –  Feb 25 '16 at 23:07
  • @PyRulez I somehow assumed changing the type was disallowed, without no particular reason. Your type looks intriguing... you could expand it into a proper answer? – chi Feb 25 '16 at 23:10
  • @chi That won't help solve this. I just saying that it is more of a "pure applicative functor" than the current type. – PyRulez Feb 25 '16 at 23:13
  • @Ana My intuition is that without monads, the _control_ of the computation is statically determined. E.g., if IO were an applicative but not a monad, we could not ask for input and, depending on that, performing some output. Even `getLine >>= putStrLn` can not be done with applicatives only. Here you want the parser to fail whenever the "input" is not a digit, and I'm guessing this is impossible to do reusing `item`. – chi Feb 25 '16 at 23:16
  • 1
    @chi and Ana https://gist.github.com/ChristopherKing42/cb30809332ccb9d7a6bd – PyRulez Feb 25 '16 at 23:38

2 Answers2

2

Functors allow you to act on somethings values. For example, if you have a list [1,2,3], you can change the contents. Note that Functors do not allow changing structure. map can not change the length of a list.

Applicatives allow you to combine structure, and the content is mushed together somehow. But the values can not change influence the structure.

Namely, given an item, we can change its structure, and we can change its content, but the content can not change the structure. We can't choose to fail on some content and not other.

If anyone knows how to state this more formally and provably, I'm all ears (it probably has to do with free theorems).

PyRulez
  • 10,513
  • 10
  • 42
  • 87
  • As chi said, interested in seeing the expanded implementation. Perhaps you would give an alternative implementation in your answer based on the type you provided. –  Feb 25 '16 at 23:24
  • @Ana It simply isn't possible with Applicative. (I did post my implementation in the comments, which also contains a monad based one in the comments.) – PyRulez Feb 25 '16 at 23:40
  • @Ana Tell me if you have any questions about it (or add a comment directly to the gist.) – PyRulez Feb 25 '16 at 23:41
  • @Ana Here is the monad implementation: https://gist.github.com/ChristopherKing42/cb30809332ccb9d7a6bd#file-interestingparsertype-hs-L24 – PyRulez Feb 25 '16 at 23:48
  • @Ana Actually, this is the link: https://gist.github.com/ChristopherKing42/cb30809332ccb9d7a6bd/52ef0a575838e25d556fe174b73bdb3571771647#file-interestingparsertype-hs-L29 – PyRulez Feb 26 '16 at 00:03
  • @Ana It can only be "used" by converting it into other parser types, I think? (You could convert it into a state monad probably.) – PyRulez Feb 26 '16 at 10:47
  • I'd really like to understand how that implementation can be used. If I post your code as a separate question, would you expand it into a full answer with example? –  Feb 26 '16 at 15:46
  • @Ana Sure, since it doesn't really answer this question, and comments are awkward. – PyRulez Feb 26 '16 at 17:35
2

First, let us write down all the tools we currently have at hand:

-- Data constructor
Parser :: (String -> [(a, String)]) -> Parser a

-- field accessor
parse :: Parser a -> String -> [(a, String)]

-- instances, replace 'f' by 'Parser'
fmap  :: Functor f     =>   (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
pure  :: Applicative f =>                 a -> f a

-- the parser at hand
item :: Parser Char

-- the parser we want to write with item
digit :: Parser Char
digit = magic item

-- ?
magic :: Parser Char -> Parser Char

The real question at hand is "what is magic"? There are only so many things we can use. Its type indicates fmap, but we can rule that out. All we can provide is some function a -> b, but there is no f :: Char -> Char that makes fmap f indicate a failure.

What about (<*>), can this help? Well, again, the answer is no. The only thing we can do here is to take the (a -> b) out of the context and apply it; whatever that means in the context of the given Applicative. We can rule pure out.

The problem is that we need to check the Char that item might parse and change the context. We need something like Char -> Parser Char

But we didn't rule Parser or parse out!

magic p = Parser $ \s ->
  case parse p s of -- < item will be used here
    [(c, cs)] -> if isDigit c then [(c, cs)] else []
    _         -> []

Yes, I know, it's duplicate code, but now it's using item. It's using item before inspecting the character. That's the only way we can use item here. And now, there is some kind of sequence implied: item has to succeed before digit can do it's work.

Alternatively, we could have tried this way:

digit' c :: Char -> Parser Char
digit' c = if isDigit c then pure c else empty

But then fmap digit' item would have the type Parser (Parser Char), which can only get collapsed with a join-like function. That's why monads are more powerful than applicative.

That being said, you can get around all of the monad requirements if you use a more general function first:

satisfy :: (Char -> Bool) -> Parser Char
satisfy = Parser $ \s -> 
   case s of
     (c:cs) | p c -> [(c, cs)]
     _            -> []

You can then define both item and digit in terms of satisfy:

item  = satisfy (const True)
digit = satisfy isDigit

That way digit does not have to inspect the result of a previous parser.

Community
  • 1
  • 1
Zeta
  • 103,620
  • 13
  • 194
  • 236