tl;dr: you want this expression:
betaLine = string "BETA " *> (BetaPair <$> p_int <*> p_int <*> p_int <*> p_int <*> p_direction <*> p_exposure) <* eol
Read why below.
Once again, this is partly a precedence issue. What your current line does:
string "BETA " *> p_int <*> p_int ...
... is that it creates a parser like this:
(string "BETA " *> p_int) <*> (p_int) ...
This is not the main issue, though, and as a matter of fact, the semantically wrong parser above would still yield the correct result, if the rest of the parser were correct. However, as you say, you have a slight misunderstanding about how <*>
works. Its signature is:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
As you can see, the function should get a function wrapped in a functor as the first argument, which it then applies using the value wrapped in the functor in the second argument (thus the applicative functor). When you give it p_int
as the first argument at the beginning of your function, it is a Parser Int
and not a Parser (a -> b)
, so the types don't check.
And as a matter of fact, they cannot be made to type check if the goal is what you stated with your reasoning; you want betaLine
to be a Parser (Int -> Int -> Int -> Int -> Direction -> ExposureList)
, but how would that help you? You get a function that takes 4 Int
s, a Direction
and ExposureList
, and when you give that function to the constructor of a BetaPair
, it is magically supposed to construct a BetaPair
out of it? Remember that functions associate to the right, so if the BetaPair
constructor has type:
Int -> Int -> Int -> Int -> Direction -> ExposureList -> BetaPair
... it doesn't mean the same thing as:
(Int -> Int -> Int -> Int -> Direction -> ExposureList) -> BetaPair
It actually means this:
Int -> (Int -> (Int -> (Int -> (Direction -> (ExposureList -> BetaPair)))))
You can instead make the betaLine
be a Parser BetaPair
, which would make more sense. You can use the <$>
operator, which is a synonym for fmap
(under the function arrow), which lets you lift your BetaPair
constructor into the Parser
functor, and then apply individual arguments to it using the applicative functor interface. The <$>
function has this type:
(<$>) :: Functor f => (a -> b) -> f a -> f b
In this case, your first argument that you're lifting is the BetaPair
constructor, which transforms the types a
and b
into the type components of the BetaPair
"function", yielding this specific signature:
(<$>) :: (Int -> (Int -> (Int -> (Int -> (Direction -> (ExposureList -> BetaPair))))))
-> f Int -> f (Int -> (Int -> (Direction -> (ExposureList -> BetaPair))))
As you can see, what the <$>
will do here is to take a function as the left argument, and a value wrapped in a functor as the right argument, and apply the wrapped argument to the function.
As a simpler example, if you have f :: Int -> String
, the following expression:
f <$> p_int
... will parse an integer, apply the function f
with that integer as the argument, and wrap the result in the functor, so the expression above has type Parser String
. The type of <$>
in this position is:
(<$>) :: (Int -> String) -> Parser Int -> Parser String
So, using <$>
applies the first argument to your constructor. So how do you deal with the other arguments? Well, this is where the <*>
comes in, and I think that you understand from the type signature what it does: if you chain its use, it will successively apply one more argument to the function wrapped in the functor to the left, by unwrapping the functor to the right. So, for a simpler example again; say that you have a function g :: Int -> Int -> String
and the following expression:
g <$> p_int <*> p_int
The g <$> p_int
expression will apply the result of p_int
to the first argument of g
, so the type of that expression is Parser (Int -> String)
. The <*>
then applies the next argument, with the specific type of <*>
being:
(<*>) :: Parser (Int -> String) -> Parser Int -> Parser String
So, the type of the whole expression above is Parser String
.
Equivalently, for your situation, you can let BetaPair
be your g
in this case, yielding this pattern:
BetaPair <$> one <*> parser <*> per <*> argument <*> to <*> betaPair
As mentioned above, the resulting parser is thus:
betaLine = string "BETA " *> (BetaPair <$> p_int <*> p_int <*> p_int <*> p_int <*> p_direction <*> p_exposure) <* eol
(*>)
and(<*)
so it could get by with minimal parens. – stephen tetley(<*)
or(*>)
has to be parenthesized so the parser fragment in the third line would be something like this:((many (char ' ') *> dir) <* many (char ' '))
– stephen tetleystring "BETA " *> p_int
andp_exposure <* eol
does not fix it. – Noah Daniels