[web-devel] Type-safe URL handling
Chris Eidhof
chris at eidhof.nl
Tue Mar 16 12:54:36 EDT 2010
On 16 mrt 2010, at 16:46, Michael Snoyman wrote:
> Took me a bit to appreciate what you just said there, but I see your point now. It's true, it does have some very nice features. I'm still concerned about creating something which involves too much boilerplate.
Yes. Generic programming (which is what the regular library provides) tries to hide the boilerplate (Template Haskell) code in a library and provides you with combinators so that you can program on the structure of a datatype. This is also at the core of my regular-web library [1], which generates forms/html/json/xml in the same way. You notice that on lines 34-36, I use the exact same TH calls. Once you did that, you get HTML and Formlets generation for free!
>
> Am I understanding correctly that the URLs will be derived from the names of the datatypes?
Exactly!
> Also, how would you address URL dispatch in this approach?
A URL is represented by a datastructure, e.g. ApplicationRoute. You would write a function "dispatch :: ApplicationRoute -> Application". Dispatch is not part of the library (and it shouldn't be, imo). In the module "MyApp.UserController" you might write a function "dispatchUser :: UserRoute -> Application", which is called by the original dispatch function.
-chris
[1]: http://github.com/chriseidhof/regular-web/blob/master/Example.lhs
>
> On Tue, Mar 16, 2010 at 8:24 AM, Chris Eidhof <chris at eidhof.nl> wrote:
> Creating a number of datatypes (each for every "component") is interesting because of two things:
>
> * You can only produce valid URLs (you get that for free)
> * You can provide an individual component as a library (e.g. a hackage package)
>
> I guess that all these approaches can be made equivalent, it's mostly a matter of style and preference. What I like about my approach is that it's very light on Template Haskell: the only TH comes from the regular library and is well-tested.
>
> -chris
>
> On 16 mrt 2010, at 16:16, Michael Snoyman wrote:
>
> > 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 <chris at eidhof.nl> wrote:
> > 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 <chris at eidhof.nl> 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 at haskell.org
> > > http://www.haskell.org/mailman/listinfo/web-devel
> > >
> >
> > _______________________________________________
> > web-devel mailing list
> > web-devel at haskell.org
> > http://www.haskell.org/mailman/listinfo/web-devel
> >
>
>
More information about the web-devel
mailing list