
On Wed, Mar 17, 2010 at 12:48 PM, Jeremy Shaw
On Tue, Mar 16, 2010 at 11:05 PM, Michael Snoyman
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 arguments.
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:
http://src.seereason.com/urlt/WaiExample.hs
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 relevant.
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: Mine: 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 immediately divisive. 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? Michael