[web-devel] Type-safe URL handling

Michael Snoyman michael at snoyman.com
Tue Mar 16 20:15:00 EDT 2010

On Tue, Mar 16, 2010 at 3:00 PM, Jeremy Shaw <jeremy at n-heptane.com> wrote:

> On Tue, Mar 16, 2010 at 11:54 AM, Chris Eidhof <chris at eidhof.nl> wrote:
>> 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!
> Using URLT.TH you would just need the one-liner:
> $(deriveAsURL ''UserRoute)
> However, if that is asking too much, I have also added URLT.Regular:
> http://src.seereason.com/urlt/URLT/Regular.hs
> So instead you do:
> $(deriveAll ''UserRoute "PFUserRoute")
> type instance PF UserRoute = PFUserRoute
> instance AsURL UserRoute where
>   toURLS   = gtoURLS . from
>   fromURLC = fmap (fmap to) gfromURLC
>> > Am I understanding correctly that the URLs will be derived from the
>> names of the datatypes?
>> Exactly!
> URLT currently allows you to generate the urls from the names via template
> haskell, generics, or by writing instances by hand. I would like to add
> support for QuasiQuotes similar to what is done in Yesod.
> > 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.
> That is how URLT works -- except better. Your function type will be like:
> dispatchApp :: (ShowURL m, URL m ~  ApplicationRoute) => ApplicationRoute
> -> m a
> MyApp.UserController might have a function like:
> dispatchUser  :: (ShowURL m, URL m ~ UserRoute) => UserRoute -> m a
> the top level dispatchApp would call it like:
> dispatchApp (User userURL) = nestURL User $ dispatchApp userURL
> The constraints ensure that your app is also only generating URLs of type
> ApplicationRoute. If your app was generating urls of type UserRoute, but
> expecting incoming urls of type ApplicationRoute, that clearly would not
> work.
> Imagine if you accidentally wrote:
> dispatchApp Login =
>          do let url = toURL List
>               in <a href=list>list</a>
> Here, in the dispatchApp function I accidentally called 'toURL List'
> instead of, 'toURL (User List)'. In your code that is *not* caught as a type
> error. With the ShowURL monad it is caught as a compile time error. If the
> goal is type-safe URLs I think it is essential to catch this error, don't
> you?
>  - jeremy

Firstly, I haven't read through all of URLT, so take anything I say with a
grain of salt. I'll happily be corrected where I misspeak.

I'm not sure if I really see the big advantages for URLT over the code that
I posted. As an advantage to my code, it's *much* smaller and easier to
digest. I understand the URLT is doing a lot of other stuff with TH and the
like, but I'm trying to look at the core of URL dispatch here. I would
imagine having a system like this:

* An underlying typeclass/datatype/whatever for defining a set of URLs. For
lack of a better term, let's call it a WebSubapp.
* The ability to embed a WebSubapp within another WebSubapp.
* The ability to convert a resource in a WebSubapp into a relative path, and
* Dispatch a request to a specific resource, passing also (either via
explicit argument or through a Reader monad) a function to convert a
resource into an absolute path.
* Convert a WebSubapp into an Application. I'll assume for the moment that
would be a Network.Wai.Application.

Once we had that skeleton, we could dress it up however we want. For Yesod,
I would change the mkResources quasi-quoter to produce an instance of
WebSubapp. Others may wish to use the regular package, some might use TH,
and others still may code it all directly.

However, if we keep the same skeleton, then all of these will operate with
each other seemlessly.

The one piece of the puzzle that still irks me just a bit is initialization,
such as creating database connections, loading settings, etc. I have some
ideas on that as well, but I'll wait to discuss them after we get some of
the more basic components addressed.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/web-devel/attachments/20100316/b80de646/attachment-0001.html

More information about the web-devel mailing list