Another quick fix for an annoyance I found whilst working my way through the Real World Haskell book. This time it’s in Chapter 16 where we are learning about applicative functors and how to use Parsec in an applicative stylee rather than monadically.
As it turns out there have been some changes and the now current Parsec3 already has Applicative and Alternative instances. So the need for a seperate ApplicativeParsec module is obviated, as noted in the chapter comments by Brian Lewis. I made some changes to use the Parsec 3 applicative parts, but then got stuck with the following error generated by the a_hex method
No instance for (Text.Parsec.Prim.Stream s0 m0 Char)
arising from a use of `char'
add an instance declaration for
(Text.Parsec.Prim.Stream s0 m0 Char)
In the first argument of `(*>)', namely char '%'
In the expression: char '%' *> (hexify <$> hexDigit <*> hexDigit)
In an equation for `a_hex':
= char '%' *> (hexify <$> hexDigit <*> hexDigit)
hexify a b = toEnum . fst . head . readHex $ [a, ....]
As you can imagine it was all a bit head scratchy, but in the end after some googleing about I found that I had merely to add a type declaration to my a_hex functoin. Presumably there is some kind of problem with the compiler’s ability to infer our type correctly here. I also had to write an a_query function and I put a testFA function in as a convenience. I needed to import Numeric. Finally, I switched round the order of a_hex to make it clearer what was going on as suggested by Petr Prokhorenkov and Paul Keir in the wiki comments. My final code was this.
import Control.Applicative hiding (many,optional,(<|>))
a_hex :: CharParser () Char
char '%' *> (hexify <$> hexDigit <*> hexDigit)
where hexify a b = toEnum . fst . head . readHex $ [a,b]
a_char :: CharParser () Char
a_char = oneOf urlBaseChars
<|> (' ' <$ char '+')
liftA2 (,) (many1 a_char) (optionMaybe (char '=' *> many a_char))
a_query :: CharParser () [(String, Maybe String)]
a_query = a_pair_app1 `sepBy` char '&'
urlBaseChars = ['a'..'z']++['A'..'Z']++['0'..'9']++"$-_.!*'(),"
testFA = parseTest a_query "foo=bar&a%21=b+c"
That worked as expected and I could move on from parsing URL strings to parsing some JSON!
Prelude> :l FormApp.hs
[1 of 1] Compiling Main ( FormApp.hs, interpreted )
Ok, modules loaded: Main.
Loading package bytestring-0.9.2.1 ... linking ... done.
Loading package transformers-0.3.0.0 ... linking ... done.
Loading package mtl-2.1.1 ... linking ... done.
Loading package array-0.4.0.0 ... linking ... done.
Loading package deepseq-184.108.40.206 ... linking ... done.
Loading package text-0.11.2.0 ... linking ... done.
Loading package parsec-3.1.2 ... linking ... done.
[("foo",Just "bar"),("a!",Just "b c")]
*Main> parseTest a_query ("foo=bar&baz=beep&")
parse error at (line 1, column 18):
unexpected end of input
expecting "+" or "%"
*Main> parseTest a_query ("foo=bar&baz=beep&moo")
[("foo",Just "bar"),("baz",Just "beep"),("moo",Nothing)]