[web-devel] Type-safe URL handling

Michael Snoyman michael at snoyman.com
Tue Mar 16 11:46:31 EDT 2010


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.

Am I understanding correctly that the URLs will be derived from the names of
the datatypes? Also, how would you address URL dispatch in this approach?

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
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/web-devel/attachments/20100316/2a5976e6/attachment-0001.html


More information about the web-devel mailing list