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.
Some people below did great jobs on "step-by-step" guides for you to easily understand the progress of computation to create the final result. So I don't replicate it here.
What I think is that, you really need to deeply understand about Functor and Applicative Functor. Once you understand these topics, the others will be easy as one two three (I means most of them ^^).
So: what is Functor, Applicative Functor and their applications in your problem?
Best tutorials on these:
Chapter 11 of "Learn You a Haskell for a great good": http://learnyouahaskell.com/functors-applicative-functors-and-monoids.
More visual "Functors, Applicatives, And Monads in Pictures": http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html.
First, when you think about Functor, Applicative Functor, think about "values in contexts": the values are important, and the computational contexts are important too. You have to deal with both of them.
The definitions of the types:
The value here is the value of type
a
, the first element of the tuple in the list.The context here is the function, or the eventual value. You have to supply an input to get the final value.
Parser
is a function wrapped in aP
data constructor. So if you got a valueb :: Parser Char
, and you want to apply it to some input, you have to unwrap the inner function inb
. That's why we have the functionparse
, it unwraps the inner function and applies it to the input value.And, if you want to create
Parser
value, you have to useP
data constructor wraps around a function.Second, Functor: something that can be "mapped" over, specified by the function fmap:
I often call the function
g :: (a -> b)
is a normal function because as you see no context wraps around it. So, to be able to applyg
tof a
, we have to extract thea
fromf a
somehow, so thatg
can be apply toa
alone. That "somehow" depends on the specific Functor and is the context you are working in:g
is the function of type(a -> b)
,p
is of typef a
.To unwrap
p
, to get the value of of context, we have to pass some input value in:parse p inp
, then the value is the 1st element of the tuple. Applyg
to that value, get a value of typeb
.The result of
fmap
is of typef b
, so we have to wrap all the result in the same context, that why we have:fmap g p = P (\inp -> ...)
.At this time, you might be wonder you could have an implementation of
fmap
in which the result, instead of[(g v, out)]
, is[(g v, inp)]
. And the answer is Yes. You can implementfmap
in any way you like, but the important thing is to be an appropriate Functor, the implementation must obey Functor laws. The laws are they way we deriving the implementation of those functions (http://mvanier.livejournal.com/4586.html). The implementation must satisfy at least 2 Functor laws:fmap id = id
.fmap (f . g) = fmap f . fmap g
.fmap
is often written as infix operator:<$>
. When you see this, look at the 2nd operand to determine which Functor you are working with.Third, Applicative Functor: you apply a wrapped function to a wrapped value to get another wrapped value:
In your case:
pg
is of typef (a -> b)
,px
is of typef a
.g
frompg
byparse pg inp
,g
is the 1st of the tuple.px
and applyg
to the value by usingfmap g px
. Attention, the result function only applies toout
, in some case that is"bc"
not"abc"
.P (\inp -> ...)
.Like Functor, an implementation of Applicative Functor must obey Applicative Functor laws (in the tutorials above).
Fourth, apply to your problem:
f1 <*> f2
by passing"abc"
to it:f1
by passing"abc"
to it, we get[(g, "abc")]
.fmap
g
onf2
and passingout="abc"
to it:f2
get[('a', "bc")]
.g
on'a'
get a result:[(\y -> ('a', y), "bc")]
.fmap
1st element of the result onf3
and passingout="bc"
to it:f3
get[('b', "c")]
.'b'
get final result:[(('a', 'b'), "c")]
.In conclusion:
Happy Haskell hacking!
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 withparse
.In running the parser you give the
Parser
and input string toparse
and it will give you the list of possible parses. I think that's probably easy to understand.You will pass
parse
aParser
, which may be built using glue,<*>
. Try to understand that when you passparse
theParser
,a
, or theParser
,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 typea
. You can think of a term such asas a series of computations which return some data, say
a
thenb
thenc
. In the context ofParser
, the computation involvef
looking fora
in the current string, then passing the remainder of the string tog
, 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,
I personally find the mental model of emitting and collecting helpful.
So, with that long preamble over, onto the actual explanation. What does
do? Well,
parse (p::Parser (Char,Char) "abc"
applies the parser, (which I renamedp
) 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
takes the next character from the string, yields it as a result and passes the unconsumed input. The nextitem
does the same. The beginning can be rewritten as:or
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.
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:Working by equational reasoning can oftentimes be easier:
(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);
(parsing with
pure v
produces thev
without consuming the input);(parsing with
item
means taking oneChar
off the head of the inputString
, if possible); and,(
<*>
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 isthe rightmost
<*>
is the topmost, so we expand it first, leaving the nested expression as is for now,Similarly, the nested expression is simplified as
and thus we get
I think you can see now why the result will be
[( ((,) 'a') 'b', "c")]
.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).
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 caseParser a
).Using
pure
we lift the function(,)
into the same "context/container" our values are in (note, that we can usepure
to lift the function into any Applicative (Maybe, List, Parser, . . ):Using
<*>
we can apply the function(,)
that is now inside theParser
context to a value that is also inside theParser
context. One difference to the example you provided with+1
is that(,)
has two arguments. Therefore we have to use<*>
twice: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:
<$>
is justfmap
but you can use it to write the combinators more beautifully: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 . . :)
Hmm I am not experienced with Haskell but my attempt on generating
Functor
andApplicative
instances of theParser
type would be as follows;(10x very much for the helping of @Will Ness on
<*>
)Accordingly...
Let's evaluate
pure (\x y -> (x,y)) <*> item
. The second application of<*>
will be easy once we've seen the first:We replace the
<*>
expression with its definition, substituting the expression's operands for the definition's parameters.Then we do the same for the
fmap
expression.Now we can reduce the first two
parse
expressions (we'll leaveparse item out
for later since it's basically primitive).So much for
pure (\x y -> (x,y)) <*> item
. Since you created the first parser by lifting a binary function of typea -> b -> (a, b)
, the single application to a parser of typeParser Char
represents a parser of typeParser (b -> (Char, b))
.We can run this parser through the
parse
function with input"abc"
. Since the parser has typeParser (b -> (Char, b))
, this should reduce to a value of type[(b -> (Char, b), String)]
. Let's evaluate that expression now.By the definition of
parse
this reduces toClearly, 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.
Now we finally evaluate that last
parse
expression.parse item "abc"
clearly reduces to[('a', "bc")]
from the definition ofitem
.Again, the second pattern matches and we do substitution
which reduces to
If you apply this same process to evaluate a second
<*>
application, and put the result in theparse
(result)"abc"
expression, you'll see that the expression indeed reduces to[(('a','b'),"c")]
.