[web-devel] Type-safe URL handling
michael at snoyman.com
Wed Mar 17 18:47:42 EDT 2010
On Wed, Mar 17, 2010 at 12:48 PM, Jeremy Shaw <jeremy at n-heptane.com> wrote:
> On Tue, Mar 16, 2010 at 11:05 PM, Michael Snoyman <michael at snoyman.com>wrote:
>> My new version has two typeclasses, I'll copy them in their entirety here:
>> class IsRelPath a where
>> toRelPath :: a -> RelPath
>> fromRelPath :: RelPath -> Maybe a
>> class IsRelPath (Routes a) => WebPlug a where
>> type Routes a
>> dispatch :: a -> Routes a -> (Routes a -> AbsPath) -> Application
>> I think we all agree that IsRelPath 1) needs to exist and 2) should be
>> called something better than that. I would say that it's useful to have
>> dispatch as part of a typeclass, which is what WebPlug now is. What makes
>> this typeclass so convenient is that any instance of WebPlug is *self
>> contained*. There's no need to keep track of which subapps require which
> I am not really clear wath the benefit for WebPlug is -- it seems to me
> that it is just adding more boilerplate..
> I added two new modules to URLT, namely URLT.Wai and URLT.Dispatch.
> I think implemented your little blog demo twice. Once where I didn't use
> dispatch, and once were I did. The code for that is here:
> It seemed like using dispatch did not get rid of or simplify anything, it
> just added more boiler plate, type classes, and used extensions (type
> families) that a lot of people don't understand yet.
> And instead of writing something short a straigt-forward like:
> handleWai mkAbs fromAbs (mySite now)
> I had to write the longer:
> handleWai mkAbs fromAbs (dispatch (SiteArgs (BlogArgs now)))
> And on the handler end, it hide useful information in the type signature
> and required more constructor matching. Instead of:
> myBlog :: UTCTime -> (BlogURL -> String) -> BlogURL -> Application
> myBlog now mkAbs BlogHome _request =
> I have:
> myBlogD :: BlogArgs -> (BlogURL -> String) -> BlogURL -> Application
> myBlogD (BlogArgs now) mkAbs BlogHome _request =
> In order to know the type of 'now' I have to go look somewhere else. In
> 'myBlog' it was right there in the type signature.
> So, I guess I do not yet see the value of Dispatch. On the plus side, it
> doesn't seem like I have to use it if I don't like it. But I am curious if I
> am missing something useful here..
> One advantage is that I can do:
> :info Dispatch
> in GHCi, and see all the Dispatch instances that are available. But I'm not
> sure that really makes it worth the effort.
> - jeremy
> p.s. The WaiExample does not use AsURL / IsRelPath, because that is really
> a completely orthogonal issue, and I wanted to cut out anything that was not
Firstly, I think the most valid concern about my appoach is that it uses
TypeFamilies. I grant that 100%.
Now, as far as your concerns about boilerplate and hiding of types: you're
correct on the small scale. When dealing with simple examples, it makes
perfect sense to just pass in the 2 or 3 arguments directly instead of
having a datatype declared. I see the advantage of having a unified
typeclass/dispatch function for dealing with large, nested applications.
That said, your example and my example are not exactly the same. I find the
final line of mine to be *much* more concise than your Dispatch version.
Let's compare them directly:
run 3000 $ plugToWai (MySite $ Blog now) "http://localhost:3000/"
Your dispatch version:
run 3000 $ handleWai mkAbs fromAbs (dispatch (SiteArgs (BlogArgs now)))
Your handleWai version:
run 3000 $ handleWai mkAbs fromAbs (mySite now)
I think a lot of the boilerplate you experienced comes from your
implementation of my idea, not the idea itself.
However, let's try to deal with some of the other important issues. Firstly,
Failing versus Maybe: I can't really see a case when you'd need to specify
why the path is not a valid URL. It would seem that either it's a
theoretically valid path, or it's not. Issues like "that object doesn't
exist" wouldn't be handled at the dispatch level usually.
I still think we need to reconsider relying on one or the other monad
transformer library. I notice now that you're using mtl; Yesod uses
transformers. I don't really have a strong preference on this, but it's
There's one other major difference between URLT and my gist: my gist splits
a path into pieces and hands that off for parsing. Your code allows each
function to handle that itself. In your example, you use the default Read
instance (I assume for simplicity). Splitting into pieces the way I did
allowed for easy pattern matching; what would URLT code look like that
handled "real" URLs?
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the web-devel