
On Fri, Mar 26, 2010 at 10:30 AM, Michael Snoyman
I also am beginning to share a mistrust of classes; I think I went a little too overboard on them on a few previous packages (namely, convertible-text) and am now having a reaction in the opposite direction. I'm sure one day I'll find the Golden Path...
Sometimes I think that module functors or adga's module system might be part of the solution. But maybe only because I have not used those systems much ;)
* I'd like to minimize dependencies as much as possible for the basic
package. The two dependencies I've noticed are Consumer and applicative-extras. I think the type signatures would be clearer *without* those packages included, eg:
fromPathSegments :: [String] -> Either ErrMsg a
Except that is not a usable type. fromPathSegments may consume, some, but not all of the path segments. Consider the type:
data SiteURL = Foo Int Int
fromPathSegments is going to receive the path segments:
["Foo","1","2"]
If you wrote a parser by hand, you would want it to look a little something like:
do string "Foo" slash i <- fromPathSegments slash j <- fromPathSegments eol return (Foo i j)
The key concept here is that when you call fromPathSegments to get the first argument of Foo you need to know how many of the path segments were consumed / are remaining, so you can pass only those segments to the second fromPathSegments.
So you really need a type like:
fromPathSegments :: [String] -> (Either ErrMsg a, [String])
which outputs the unconsumed path segments.
Well, given that as a criterion, I agree with the rest of your analysis entirely. However, I think we're looking at the purpose of fromPathSegments very differently. I'm not quite certain I understand why we would want to output the unconsumed segments; if something is unconsumed, then it seems like it's an invalid URL and should fail.
In your example, if I request "/Foo/5/6/7", fromPathSegments would return (Right (Foo 5 6), ["7"]); but what is going to consume that 7 now? The use case I envisioned for something like this is:
data BlogRoutes = ... data MySite = MyHome | MyBlog BlogRoutes fromPathSegments ("blog":rest) = MyBlog `fmap` fromPathSegments
But what if you had, data BlogRoutes = ... data Foo = ... data MySite = MyHome | MyBlog Foo BlogRoutes Where the MyBlog constructor has *two* arguments. In theory you want to write something like: fromPathSegments ("MyBlog":rest) = MyBlog `fmap` fromPathSegments ?? `ap` fromPathSegments ??? The first fromPathSegments will parse the 'Foo' argument and the second fromPathSegments will parse the BlogRoutes argument. To make things more interesting, let's assume that Foo and BlogRoutes were defined in 3rd party modules that don't even know about each other your app. The problem is, what arguments do you pass to each fromPathSegments call? The first call to fromPathSegments is going to consume some of the path segments, and the second call will consume the remaining. But we do not have enough information here to know in advance how to split up 'rest' between the two calls. Instead we to run the first fromPathSegments and have it tell us what part it did not consume. If what I have said still does not make sense, then try this exercise: create 3 modules, one each for: data BlogRoutes = BlogHome data Foo = Foo Int | Bar Char Int data MySite = MyBlog Foo BlogRoutes now create fromPathSegments instances for each of those routes. I think you will find that it is difficult to implement the instance for MySite. Finally, can you now change the Foo type to: data Foo = Foo Int Int | Bar Int Char Int by *only* modifying the Foo module, and without breaking the MySite module? Regarding the type: data Foo = Foo Int Int attempting to parse: "/Foo/5/6/7" I think that should be handled in, fromPathInfo :: (PathInfo u) => String -> Failing u, when it calls fromPathSegments. At that point in time we know that all the segments should have been consumed... so if there is left over junk, something is wrong. The latest version of the code is now at: http://src.seereason.com/web-routes/ I did the renaming but have not made all the other changes yet. - jeremy