I'm a little mixed up about how to properly use boomerang to generate URLs. I have the following:
data State =
AK | AL | AR | AZ | CA ... WY
data Sitemap
= Home
| State State
| Place State String
deriving (Eq, Ord, Read, Show, Data, Typeable)
$(derivePrinterParsers ''Sitemap)
sitemap ∷ Router Sitemap
sitemap =
( rHome
<> rState . state
<> rPlace . (state </> anyString)
)
state :: PrinterParser StringsError [String] o (State :- o)
state = xmaph read (Just . show) anyString
This seems to work, but when I compare my implementation of state
with the one in the documentation for articleId
, they seem to be working in opposite fashions:
articleId :: Router ArticleId
articleId = xmaph ArticleId (Just . unArticleId) int
The types are totally different and look like they're going in opposite directions, but my sitemap
works and the app correctly handles URLs. I think it should look more like this:
maybeState :: String → Maybe State
maybeState stateString = case reads stateString of
[(state, "")] -> Just state
_ -> Nothing
stateR :: Router State
stateR = xpure show maybeState
This doesn't type-check, but even substituting undefined
for its definition, in sitemap
above, rState . stateR
would work, but rPlace . (stateR </> anyString)
doesn't.
Seems like this would come up often enough there's probably a library function to take care of this for me, but I didn't see one.
Edit: here are some of the type errors I get:
For state = xpure show maybeState
:
Main.hs:56:16:
Couldn't match expected type `State :- ()'
with actual type `[Char]'
Expected type: () -> State :- ()
Actual type: () -> String
In the first argument of `xpure', namely `show'
In the expression: xpure show maybeState
For state = undefined :: Router State
(this error is in the sitemap
definition):
Main.hs:45:18:
Couldn't match expected type `String :- ()' with actual type `()'
Expected type: PrinterParser
StringsError [String] () (State :- (String :- ()))
Actual type: Router State
In the first argument of `(</>)', namely `state'
In the second argument of `(.)', namely `(state </> anyString)'
The types look different because you use of state in the rPlace
line requires a more general type signature than the Router
type alias permits. (Your code is fine. But perhaps we should offer a more general alias in boomerang though..)
If you remove the rPlace line you can change the type signature of state to:
state :: Router State
state = xmaph read (Just . show) anyString
If you look more closely, I think you will see that state
and articleId
actually do go the same direction.
articleId :: Router ArticleId
articleId = xmaph ArticleId (Just . unArticleId) int
The third argument of xmaph
specifies how to parse some underlying value. In the case of articleId
it parses an int
and for state
it parses anyString
.
The first argument of xmaph
specifies how to convert that value to the desired return type. In articleId
we simple apply the ArticleId
constructor. In state
we apply the read
function. But in both cases we are going from the underlying value to the desired return type:
ArticleId :: Int -> ArticleId
read :: String -> State
The second argument to xmaph
specifies how to convert the return type back to the underlying value.
show :: State -> String
unArticleId :: ArticleId -> Int
That said, we should not actually be using 'read' here anyway because 'read' could potentially fail and through an error. It is intended that the first argument to xmaph will be a total function.
I uploaded boomerang 1.3.1 which adds a new combinator to the Strings
module named readshow
. This function uses the Read and Show instances correctly. Unfortunately, error reporting is a bit sloppy since when reads
fails it tells us nothing about why or where it failed. But it's better than nothing :)
Using that you could now write:
state :: PrinterParser StringsError [String] o (State :- o)
state = readshow
if we supply an invalid state we now get:
> parseStrings sitemap ["AZ"]
Right (State AZ)
> parseStrings sitemap ["FOEU"]
Left parse error at (0, 0): unexpected FOEU; decoding using 'read' failed.