Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

applicative functor: <*> and partial application, how it works

I am reading the book Programming in Haskell by Graham Hutton and I have some problem to understand how <*> and partial application can be used to parse a string.

I know that pure (+1) <*> Just 2 produces Just 3 because pure (+1) produces Just (+1) and then Just (+1) <*> Just 2 produces Just (2+1) and then Just 3

But in more complex case like this:

-- Define a new type containing a parser function
newtype Parser a = P (String -> [(a,String)])

-- This function apply the parser p on inp
parse :: Parser a -> String -> [(a,String)]
parse (P p) inp = p inp

-- A parser which return a tuple with the first char and the remaining string
item :: Parser Char
item = P (\inp -> case inp of
    []     -> []
    (x:xs) -> [(x,xs)])

-- A parser is a functor
instance Functor Parser where
  fmap g p = P (\inp -> case parse p inp of
    []              -> []
    [(v, out)]      -> [(g v, out)])

-- A parser is also an applicative functor
instance Applicative Parser where
  pure v = P (\inp -> [(v, inp)])
  pg <*> px = P (\inp -> case parse pg inp of
    []              -> []
    [(g, out)]      -> parse (fmap g px) out)

So, when I do:

parse (pure (\x y -> (x,y)) <*> item <*> item) "abc"

The answer is:

[(('a','b'),"c")]

But I don't understand what exactly happens. First:

pure (\x y -> (x,y)) => P (\inp1 -> [(\x y -> (x,y), inp1)])

I have now a parser with one parameter.

Then:

P (\inp1 -> [(\x y -> (x,y), inp1)]) <*> item 
=> P (\inp2 -> case parse (\inp1 -> [(\x y -> (x,y), inp1)]) inp2 of ??? 

I really don't understand what happens here. Can someone explain, step by step, what's happens now until the end please.

like image 683
yaa Avatar asked Aug 04 '17 19:08

yaa


People also ask

Could you comfortably explain the difference between a monad and an applicative functor?

Functors apply a function to a wrapped value: Applicatives apply a wrapped function to a wrapped value: Monads apply a function that returns a wrapped value to a wrapped value. Monads have a function >>= (pronounced "bind") to do this.

What is applicative in functional programming?

In functional programming, an applicative functor, or an applicative for short, is an intermediate structure between functors and monads.

How many methods should a functor instance have?

According to the implementation, we can map another Functor using two methods: "Pure" and "<*>". The "Pure" method should take a value of any type and it will always return an Applicative Functor of that value.

Why is functor useful?

Functors are also important because they are a building block for applicatives and monads, which are coming in future posts.


4 Answers

Let's evaluate pure (\x y -> (x,y)) <*> item. The second application of <*> will be easy once we've seen the first:

P (\inp1 -> [(\x y -> (x,y), inp1)]) <*> item 

We replace the <*> expression with its definition, substituting the expression's operands for the definition's parameters.

P (\inp2 -> case parse P (\inp1 -> [(\x y -> (x,y), inp1)]) inp2 of
    []              -> []
    [(g, out)]      -> parse (fmap g item) out)

Then we do the same for the fmap expression.

P (\inp2 -> case parse P (\inp1 -> [(\x y -> (x,y), inp1)]) inp2 of
    []              -> []
    [(g, out)]      -> parse P (\inp -> case parse item inp of
                           []              -> []
                           [(v, out)]      -> [(g v, out)]) out)

Now we can reduce the first two parse expressions (we'll leave parse item out for later since it's basically primitive).

P (\inp2 -> case [(\x y -> (x,y), inp2)] of
    []              -> []
    [(g, out)]      -> case parse item out of
                           []              -> []
                           [(v, out)]      -> [(g v, out)])

So much for pure (\x y -> (x,y)) <*> item. Since you created the first parser by lifting a binary function of type a -> b -> (a, b), the single application to a parser of type Parser Char represents a parser of type Parser (b -> (Char, b)).


We can run this parser through the parse function with input "abc". Since the parser has type Parser (b -> (Char, b)), this should reduce to a value of type [(b -> (Char, b), String)]. Let's evaluate that expression now.

parse P (\inp2 -> case [(\x y -> (x,y), inp2)] of
    []              -> []
    [(g, out)]      -> case parse item out of
                           []              -> []
                           [(v, out)]      -> [(g v, out)]) "abc"

By the definition of parse this reduces to

case [(\x y -> (x,y), "abc")] of
    []              -> []
    [(g, out)]      -> case parse item out of
                           []              -> []
                           [(v, out)]      -> [(g v, out)]

Clearly, the patterns don't match in the first case, but they do in the second case. We substitute the matches for the patterns in the second expression.

case parse item "abc" of
    []              -> []
    [(v, out)]      -> [((\x y -> (x,y)) v, out)]

Now we finally evaluate that last parse expression. parse item "abc" clearly reduces to [('a', "bc")] from the definition of item.

case [('a', "bc")] of
    []              -> []
    [(v, out)]      -> [((\x y -> (x,y)) v, out)]

Again, the second pattern matches and we do substitution

[((\x y -> (x,y)) 'a', "bc")]

which reduces to

[(\y -> ('a', y), "bc")] :: [(b -> (Char, b), String)] -- the expected type

If you apply this same process to evaluate a second <*> application, and put the result in the parse (result) "abc" expression, you'll see that the expression indeed reduces to[(('a','b'),"c")].

like image 143
Alex Avatar answered Oct 08 '22 17:10

Alex


What helped me a lot while learning these things was to focus on the types of the values and functions involved. It's all about applying a function to a value (or in your case applying a function to two values).

($)   ::                    (a -> b) ->   a ->   b
fmap  :: Functor     f =>   (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

So with a Functor we apply a function on a value inside a "container/context" (i.e. Maybe, List, . .), and with an Applicative the function we want to apply is itself inside a "container/context".

The function you want to apply is (,), and the values you want to apply the function to are inside a container/context (in your case Parser a).

Using pure we lift the function (,) into the same "context/container" our values are in (note, that we can use pure to lift the function into any Applicative (Maybe, List, Parser, . . ):

(,) ::              a -> b -> (a, b)
pure (,) :: Parser (a -> b -> (a, b))

Using <*> we can apply the function (,) that is now inside the Parser context to a value that is also inside the Parser context. One difference to the example you provided with +1 is that (,) has two arguments. Therefore we have to use <*> twice:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

x :: Parser Int
y :: Parser Char

let p1 = pure (,) <*> x :: Parser (b -> (Int, b))
let v1 =      (,)     1 ::         b -> (Int, b)

let p2 = p1 <*> y  :: Parser (Int, Char)
let v2 = v1    'a' ::        (Int, Char)

We have now created a new parser (p2) that we can use just like any other parser!

. . and then there is more!

Have a look at this convenience function:

(<$>) :: Functor f => (a -> b) -> f a -> f b

<$> is just fmap but you can use it to write the combinators more beautifully:

data User = User {name :: String, year :: Int}
nameParser :: Parser String
yearParser :: Parser Int

let userParser = User <$> nameParser <*> yearParser -- :: Parser User

Ok, this answer got longer than I expected! Well, I hope it helps. Maybe also have a look at Typeclassopedia which I found invaluable while learning Haskell which is an endless beautiful process . . :)

like image 38
Schoon Avatar answered Oct 08 '22 17:10

Schoon


TL;DR: When you said you "[now] have a parser with one parameter" inp1, you got confused: inp1 is an input string to a parser, but the function (\x y -> (x,y)) - which is just (,) - is being applied to the output value(s), produced by parsing the input string. The sequence of values produced by your interim parsers is:

-- by (pure (,)):
(,)                     -- a function expecting two arguments

-- by the first <*> combination with (item):
(,) x                   -- a partially applied (,) function expecting one more argument

-- by the final <*> combination with another (item):
((,) x) y == (x,y)      -- the final result, a pair of `Char`s taken off the 
                        -- input string, first (`x`) by an `item`, 
                        -- and the second (`y`) by another `item` parser

Working by equational reasoning can oftentimes be easier:

 -- pseudocode definition of `fmap`:
 parse (fmap g p) inp = case (parse p inp) of    -- g :: a -> b , p :: Parser a
    []              -> []                        --        fmap g p :: Parser b
    [(v, out)]      -> [(g v, out)]              -- v :: a ,           g v :: b

(apparently this assumes any parser can only produce 0 or 1 results, as the case of a longer list isn't handled at all -- which is usually frowned upon, and with good reason);

 -- pseudocode definition of `pure`:
 parse (pure v) inp = [(v, inp)]                 -- v :: a , pure v :: Parser a

(parsing with pure v produces the v without consuming the input);

 -- pseudocode definition of `item`:
 parse (item) inp = case inp of                  -- inp :: ['Char']
    []              -> []
    (x:xs)          -> [(x,xs)]                  -- item :: Parser 'Char'

(parsing with item means taking one Char off the head of the input String, if possible); and,

 -- pseudocode definition of `(<*>)`:
 parse (pg <*> px) inp = case (parse pg inp) of    -- px :: Parser a
    []              -> []
    [(g, out)]      -> parse (fmap g px) out       -- g :: a -> b

(<*> combines two parsers with types of results that fit, producing a new, combined parser which uses the first parse to parse the input, then uses the second parser to parse the leftover string, combining the two results to produce the result of the new, combined parser);

Now, <*> associates to the left, so what you ask about is

parse ( pure (\x y -> (x,y)) <*> item <*> item ) "abc"
= parse ( (pure (,) <*> item1) <*> item2 ) "abc"             -- item_i = item

the rightmost <*> is the topmost, so we expand it first, leaving the nested expression as is for now,

= case (parse (pure (,) <*> item1) "abc") of                 -- by definition of <*>
    []              -> []
    [(g2, out2)]    -> parse (fmap g2 item2) out2
                       = case (parse item out2) of           -- by definition of fmap
                            []              -> []
                            [(v, out)]      -> [(g2 v, out)]
                       = case out2 of                        -- by definition of item
                            []              -> []
                            (y:ys)          -> [(g2 y, ys)]

Similarly, the nested expression is simplified as

parse (pure (,) <*> item1) "abc"
= case (parse (pure (\x y -> (x,y))) "abc") of               -- by definition of <*>
    []              -> []
    [(g1, out1)]    -> parse (fmap g1 item1) out1
                       = case (parse item out1) of ....
                       = case out1 of
                            []              -> []
                            (x:xs)          -> [(g1 x, xs)]
= case [((,), "abc")] of                                     -- by definition of pure
    [(g1, out1)]    -> case out1 of
                            []              -> []
                            (x:xs)          -> [(g1 x, xs)]
= let { out1 = "abc" 
      ; g1   = (,)
      ; (x:xs) = out1
      }
   in  [(g1 x, xs)]
= [( (,) 'a', "bc")] 

and thus we get

= case [( (,) 'a', "bc")] of
    [(g2, out2)]    -> case out2 of
                            []              -> []
                            (y:ys)          -> [(g2 y, ys)]

I think you can see now why the result will be [( ((,) 'a') 'b', "c")].

like image 32
Will Ness Avatar answered Oct 08 '22 16:10

Will Ness


First, I want to emphasize one thing. I found that the crux of understanding lies in noticing the separation between the Parser itself and running the parser with parse.

In running the parser you give the Parser and input string to parse and it will give you the list of possible parses. I think that's probably easy to understand.

You will pass parse a Parser, which may be built using glue, <*>. Try to understand that when you pass parse the Parser, a, or the Parser, f <*> a <*> b, you will be passing it the same type of thing, i.e. something equivalent to (String -> [(a,String)]). I think this is probably easy to understand as well, but still it takes a while to "click".

That said, I'll talk a little about the nature of this applicative glue, <*>. An applicative, F a is a computation that yields data of type a. You can think of a term such as

... f <*> g <*> h

as a series of computations which return some data, say a then b then c. In the context of Parser, the computation involve f looking for a in the current string, then passing the remainder of the string to g, etc. If any of the computations/parses fails, then so does the whole term.

Its interesting to note that any applicative can be written with a pure function at the beginning to collect all those emitted values, so we can generally write,

 pure3ArgFunction <$> f <*> g <*> h

I personally find the mental model of emitting and collecting helpful.

So, with that long preamble over, onto the actual explanation. What does

parse (pure (\x y -> (x,y)) <*> item <*> item) "abc"

do? Well, parse (p::Parser (Char,Char) "abc" applies the parser, (which I renamed p) to "abc", yielding [(('a','b'),"c")]. This is a successful parse with the return value of ('a','b') and the leftover string, "c".

Ok, that's not the question though. Why does the parser work this way? Starting with:

.. <*> item <*> item

item takes the next character from the string, yields it as a result and passes the unconsumed input. The next item does the same. The beginning can be rewritten as:

fmap (\x y -> (x,y)) $ item <*> item

or

(\x y -> (x,y)) <$> item <*> item

which is my way of showing that the pure function does not do anything to the input string, it just collects the results. When looked at in this light I think the parser should be easy to understand. Very easy. Too easy. I mean that in all seriousness. Its not that the concept is so hard, but our normal frame of looking at programming is just too foreign for it to make much sense at first.

like image 29
trevor cook Avatar answered Oct 08 '22 18:10

trevor cook