0

I start discovering parsing in Haskell without any libraries. I created my own data type to implement a couple of type classes.

data Parser a = Parser {
  runParser :: String -> Maybe (a, String)
}

instance Functor Parser where
  fmap fct (Parser p) = Parser $ \str -> p str >>= \(a, str1) -> Just (fct a, str1)

instance Applicative Parser where
  pure a = Parser $ \ s -> Just (a, s)
  (Parser p1) <*> (Parser p2) = Parser $ \ str -> do
                                                    (f, str1) <- p1 str
                                                    (a, str2) <- p2 str1
                                                    Just (f a, str2)

instance Alternative Parser where
  empty = Parser $ \str -> Nothing
  (Parser p1) <|> (Parser p2) = Parser $ \str -> p1 str <|> p2 str

For each classes, I have implemented minimal functions. I understand how they work, but for Applicative I don't understand how <* and *> work. I understand that it's interesting if I want to parse something between delimiters for example

runParser (parseChar 'x' *> parseChar 'a' <* parseChar 'x') "xax"
> Just ('a',"")

I also understand, that it will return "what is in direction of the arrow", so in that case the expression between delimiters. But, what I don't understand is when my string is applied to the first parser, it return a Maybe with parsed data and the rest of the string. How the rest of the string is given to the next parser ?

  • 2
    `How the rest of the string is given to the next parser` - because that's exactly how you've defined it in your implementation of `<*>`. (`*>` and `<*` being defined in terms of `<*>`) – Robin Zigmond Sep 30 '21 at 18:56
  • 5
    I'm referring specifically to the part where you do `(f, str1) <- p1 str` and then on the next line evaluate `p2 str1` - that is *precisely* "feeding the rest of the string to the next parser" – Robin Zigmond Sep 30 '21 at 18:57
  • `newtype Parser a = .. deriving (Functor, Applicative, Alternative, Monad..) via StateT String Maybe` can be [derived via](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/deriving_via.html?highlight=derivingvia#extension-DerivingVia) the state monad transformer over `Maybe` – Iceland_jack Sep 30 '21 at 22:26

1 Answers1

2

Let’s look at how *> is defined:

(*>) :: f a -> f b -> f b
a1 *> a2 = (id <$ a1) <*> a2

(<$) :: a -> f b -> f a
(<$) = fmap . const

Expanding this definition for Parser gives

Parser p1 *> Parser p2
(id <$ Parser p1) <*> Parser p2
(fmap . const) id (Parser p1) <*> Parser p2
fmap (const id) (Parser p1) <*> (Parser p2)
-- using fmap definition:
Parser (\str -> p1 str >>= \(a, str1) -> Just (const id a, str1)) <*> Parser p2
Parser (\str -> p1 str >>= \(a, str1) -> Just (id, str1)) <*> Parser p2
-- using <*> definition:
Parser $ \str -> do
  (f, str1) <- (\str -> p1 str >>= \(a, str1) -> Just (id, str1)) str
  (a, str2) <- p2 str1
  Just (f a, str2)
Parser $ \str -> do
  (f, str1) <- p1 str >>= \(a, str1) -> Just (id, str1)
  (a, str2) <- p2 str1
  Just (f a, str2)
Parser $ \str -> do
  (_, str1) <- p1 str
  (a, str2) <- p2 str1
  Just (id a, str2)
Parser $ \str -> do
  (_, str1) <- p1 str
  (a, str2) <- p2 str1
  Just (a, str2)

Now for <*:

(<*) :: f a -> f b -> f a
(<*) = liftA2 const

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)

Expanding it:

Parser p1 <* Parser p2
liftA2 const (Parser p1) (Parser p2)
(<*>) (fmap const (Parser p1)) (Parser p2)
-- using fmap definition:
(<*>) (Parser $ \str -> p1 str >>= \(a, str1) -> Just (const a, str1)) (Parser p2)
-- using <*> definition:
Parser $ \str -> do
  (f, str1) <- (\str -> p1 str >>= \(a, str1) -> Just (const a, str1)) str
  (a, str2) <- p2 str1
  Just (f a, str2)
Parser $ \str -> do
  (f, str1) <- p1 str >>= \(a, str1) -> Just (const a, str1)
  (a, str2) <- p2 str1
  Just (f a, str2)
-- renaming a to b:
Parser $ \str -> do
  (f, str1) <- p1 str >>= \(a, str1) -> Just (const a, str1)
  (b, str2) <- p2 str1
  Just (f b, str2)
Parser $ \str -> do
  (a, str1) <- p1 str
  (b, str2) <- p2 str1
  Just (const a b, str2)
Parser $ \str -> do
  (a, str1) <- p1 str
  (_, str2) <- p2 str1
  Just (a, str2)

How the rest of the string is given to the next parser ?

In your definition of <*> in (f, str1) <- p1 str; (a, str2) <- p2 str1.

Lauren Yim
  • 12,700
  • 2
  • 32
  • 59