
These approaches will definitely work, but I'm worried that creating a whole
set of datatypes to represent URLs is overkill. In Yesod (I'm sure many of
you have seen) I use quasi-quoting for defining the resources, like such:
[$mkResources|
/user:
GET: userList
/user/find/#userid
GET: userFind
/user/name/$username
GET: userName
|]
And so on and so forth. I don't think defining UserRoute adds much, besides
making the job of the library writer a little bit easier by pushing the work
off to the user. I think the six lines above succinctly:
* define the valid routes
* define the data types for arguments
* define the appropriate mapping to handler functions for each request
method
Chris mentioned earlier to me the idea of using quasi-quoting on the link
generation side, perhaps like:
[$link|/user/find/6]
I think the only piece of the puzzle missing to combine these two together
is to have mkResources output something along the lines of:
data RoutePiece = StaticPiece String | IntPiece | StringPiece
_validRoutes :: [[RoutePiece]]
_validRoutes =
[ [StaticPiece "user"]
, [StaticPiece "user", StaticPiece "find", IntPiece]
, [StaticPiece "user", StaticPiece "name", StringPiece]
]
Now if you write
[$link|/user/find/michael]
link can look up in _validRoutes that there is no matching route and
complain at compile time.
Advantages: less typing by the user.
Disadvantages: we'll have to restrict the data types allowed, but in
practice I think people will usually want only strings and ints anyway.
Also, this approach is more complex.
Michael
On Tue, Mar 16, 2010 at 7:04 AM, Chris Eidhof
I have the feeling it adds a lot of complexity. I agree with you that, if you want modularity, your components should only provide relative URLs and need to be parameterized over how to build an absolute URL. I didn't think of that problem, and using a custom monad transformer is definitely a solution.
However, I'm always hesitant to build up stacks of monad transformers, it adds a lot of complexity. I would rather use something like typeclass, but I'm not sure yet how to do that.
-chris
On 16 mrt 2010, at 14:29, Jeremy Shaw wrote:
Hello,
It looks nearly identical, but without the URLT monad transformer.
Instead of ToURL I have the class:
class AsURL a where toURLS :: a -> ShowS fromURLC :: Consumer String (Failing a)
With is basically the same. Except toURLS returns a ShowS instead of [String]. fromURLC consumes a list of [String]. These functions are wrapped up to provide:
toURL :: (AsURL a) => a -> String fromURL :: (AsURL a) => String -> Failing a
I do not have generics based url printing/parsing, but there is no reason it could not be added. I do have template haskell based code though.
http://src.seereason.com/urlt/URLT/TH.hs
The thing you don't have is the URLT monad transformer:
http://src.seereason.com/urlt/URLT/Base.hs
Here is why you want it. Imagine you write an image gallery library:
data ImageURL = Upload | ViewImage Int
when you call toURL, you are going to get urls like, /Upload, /ViewImage/1, etc.
Now let's say I try to use your library in my application. So at first I try:
data MyApp = Upload | FooBar
But when a URL comes in, how do I know if I should decode it as MyApp or ImageURL? Do I try both and see which one succeeds? Except we both have a constructor Upload, so both will succeed. There is no way to tell with Upload the path "/Upload" is referring to.
So now I try:
data MyApp = Upload | FooBar | Images ImageURL
now I know that all incoming urls are decoded as MyApp. But there is still a problem. In my code I could write:
toUrl (Images (ViewImage 1))
but in your library code, you don't know anything about the Images constructor. So you just call,
toURL (ViewImage 1)
which generates /ViewImage/1 instead of the required /Images/ViewImage/1.
What I need is someway to tell your library code what prefix to add at the beginning. That is exactly what the URLT monad does. It just holds a function that adds a prefix to the URL.
so in your library you have:
image :: ImageURL -> URLT ImageURL m () image Upload = do ... u <- showURL (ViewImage n) ... image (ViewImage num) = ...
Instead of calling toURL, it calls showURL, which adds the context to the URL and then calls toURL on it.
And in my code I have:
myApp :: MyAPP -> URLT MyApp m () mpApp Upload = ... myApp FooBar = ... myApp (Images subURL) = nestURL Images $ images subURL
the 'nextURL Images' adds the Images context to the URLT environment. It can be used to nest multiple levels if needed:
nestURL A $ nestURL B $ nestURL Images $ showURL (ViewImage 1)
would get turned into something like:
"/A/B/Images/ViewImage/1"
What do you think?
- jeremy
On Tue, Mar 16, 2010 at 3:52 AM, Chris Eidhof
wrote: Hey everyone, I just wrote down some of my ideas about type-safe URL handling on github, it's at http://gist.github.com/333769
I think it's similar to what Jeremy is doing with his urlt package [1].
-chris
[1]: http://src.seereason.com/~jeremy/SimpleSite1.html
_______________________________________________ web-devel mailing list web-devel@haskell.org http://www.haskell.org/mailman/listinfo/web-devel
_______________________________________________ web-devel mailing list web-devel@haskell.org http://www.haskell.org/mailman/listinfo/web-devel