On Tue, Mar 16, 2010 at 7:15 PM, Michael Snoyman <span dir="ltr">&lt;<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>&gt;</span> wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<div><div></div><div class="h5">Firstly, I haven&#39;t read through all of URLT, so take anything I say with a grain of salt. I&#39;ll happily be corrected where I misspeak.</div></div><div><br></div><div>I&#39;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&#39;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&#39;m trying to look at the core of URL dispatch here. I would imagine having a system like this:</div>
</blockquote><div><br></div><div>The essence of URLT is pretty darn small. The TH, generics, and all that other stuff is not required. This is the essence of URLT:</div><div><br></div><div>newtype URLT url m a = URLT { unURLT :: ReaderT  (url -&gt; String) m a }</div>
<div>    deriving (Functor, Monad, MonadFix, MonadPlus, MonadIO, MonadTrans, MonadReader (url -&gt; String))</div><div><br></div><div>showURL :: (Monad m) =&gt; url -&gt; URLT url m String             </div><div>showURL u =</div>
<div>  do mkAbs &lt;- ask</div><div>     return (mkAbs u)</div><div><br></div><div>-- |used to embed a URLT into a larger parent url</div><div><div>nestURL :: (Monad m) =&gt; (url2 -&gt; url1) -&gt; URLT url2 m a -&gt; URLT url1 m a</div>
<div>nestURL b = withURLT (. b)</div><div><br></div></div><div>It&#39;s just one newtype wrapper around ReaderT and two very simple functions. No classes, no generics, no nothing..</div><div><br></div><div>To &#39;run&#39; the URLT monad transformer (ie. go from &#39;URLT url m a&#39; to &#39;m a&#39;) we simple supply a simple function of the type (url -&gt; String). </div>
<div><br></div><div>That is all that is required.</div><div><br></div><div>To dispatch the incoming url we need a function that goes from (String -&gt; url). And then we just write a plain old function that takes the type &#39;url&#39; as an argument. So for the dispatch portion we don&#39;t require any classes or anything from the library itself.</div>
<div><br></div><div>The Template Haskell, Generics, etc, are just there to provide some various ways of automatically deriving the (url -&gt; String) and (String -&gt; url) functions.</div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<div></div><div>* An underlying typeclass/datatype/whatever for defining a set of URLs. For lack of a better term, let&#39;s call it a WebSubapp.</div></blockquote><div><br></div><div>This would either refer to the monad URLT parameterized with a url type.</div>
<div><br></div><div>e.g., URLT WebURL m a</div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;"><div><br></div></blockquote><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<div>* The ability to embed a WebSubapp within another WebSubapp.</div></blockquote><div><br></div><div>the nestURL function.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">

<div>* The ability to convert a resource in a WebSubapp into a relative path, and vice-versa.</div></blockquote><div><br></div><div>showURL converts a relative path to an absolute.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<div>* 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.</div></blockquote><div><br></div><div>To dispatch a url you simple call the top-level handling function and pass in the url. The URLT environment holds the function to convert a resource into an absolute path.</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<div>* Convert a WebSubapp into an Application. I&#39;ll assume for the moment that would be a Network.Wai.Application.</div></blockquote><div><br></div><div>In the current URLT I have a function that does this for happstack. (that is the entire reason why URLT depends on happstack, and why it would be easy to split out). I can write a similar module for Wai tomorrow.</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;"><div>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.</div>
</blockquote><div><br></div><div>In URLT mkResources would just need to return the two functions (String -&gt; url) and (url -&gt; String). </div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<div>However, if we keep the same skeleton, then all of these will operate with each other seemlessly.</div></blockquote><div><br></div><div>Yes. TH and Regular already operate seamless in URLT. If you add mkResource it would as well.</div>
<div><br></div><div>Let&#39;s examine WebPlug more closely.</div><div><br></div><div><div>class WebPlug a where</div><div>    toRelPath :: a -&gt; RelPath</div><div>    fromRelPath :: RelPath -&gt; Maybe a</div><div>    dispatch :: a -&gt; (a -&gt; AbsPath) -&gt; Application</div>
</div><div><br></div><div>now, I think that having dispatch be part of the WebPlug class itself is a problem because it assumes that your dispatch function needs no other arguments besides the URL. I find that  is often not the case. For an image gallery library, the dispatch function might need to take a FilePath argument which specifies where the image directory is. So I think it is better that you write a dispatch handler with a unique name for url type and call it by its unique name. Then there is no problem if you want to add other arguments.</div>
<div><br></div><div>So, now you have a function like:</div><div><br></div><div>dispatchBlog :: a -&gt; (a -&gt; AbsPath) -&gt; Application.</div><div><br></div><div>now in my function I might want to generate a url that I will use as an href value. It&#39;s can&#39;t be a relative url (obviously) it needs to be an absolute url. So I need to do something like this:</div>
<div><br></div><div>dispatchBlog Foo mkAbs = </div><div>         let u = mkAbs (BlogPost 1)</div><div>         in &lt;a href=u&gt;Blog Post 1&lt;/a&gt;</div><div><br></div><div>Well, it can be a bit annoying to have to have to explicitly have that extra mkAbs argument on every pattern. So we could just wrap it up in ReaderT monad if we wanted:</div>
<div><br></div><div>dispatchBlog :: a -&gt; Reader (a -&gt; AbsPath) Application</div><div><br></div><div>and mkAbs can be:</div><div><br></div><div>mkAbs :: a -&gt; Reader (a -&gt; AbsPath) AbsPath</div><div>mkAbs url =</div>
<div>   do f &lt;- ask</div><div>        return (f url)</div><div><br></div><div>and we can use it like:</div><div><br></div><div>dispatchBlog Foo = </div><div>       do u &lt;- mkAbs (BlogPost 1)</div><div>            &lt;a href=u&gt;Blog Post 1&lt;/a&gt;</div>
<div><br></div><div>and were you currently have this:</div><div><br></div><div>     dispatch (MyBlog b) toAbsPath req = dispatch b (toAbsPath . MyBlog) req</div><div><br></div><div>we would have something like:</div><div>
<div><br class="Apple-interchange-newline">dispatchMyBlog (MyBlog b) = withReader (MyBlog .) $ dispatchBlog b</div><div><br></div></div><div>we can rename withReader to make its intentions more clear:</div><div><br></div>
<div>dispatchSub c = withReader (c .)</div><div><br></div><div>and just write:</div><div><div><br class="Apple-interchange-newline">dispatchMyBlog (MyBlog b) = dispatchSub MyBlog $ dispatchBlog b</div><div><br></div><div>
Since we got rid of the dispatch function in WebPlug we now have:</div><div><br></div><div><div>class WebPlug a where</div><div>    toRelPath :: a -&gt; RelPath</div><div>    fromRelPath :: RelPath -&gt; Maybe a</div></div>
<div><br></div><div>Personally, I think it should return Failure a instead of Maybe a, because we can include information about why it failed.</div><div><br></div><div><div>class WebPlug a where</div><div>    toRelPath :: a -&gt; RelPath</div>
<div>    fromRelPath :: RelPath -&gt; Failing a</div><div><br></div><div>Now, this class is useful, but not required. It is also essentially the same as AsURL</div><div><br></div><div>We have a low-level function:</div><div>
<br></div><div>plugToWai&#39; :: (a -&gt; ReaderT (a -&gt; AbsPath) Application) -- ^ the dispatch function </div><div>                  -&gt; (a -&gt; AbsPath) -- ^ function to convert url to an AbsPath </div><div>                  -&gt; (AbsPath -&gt; a) -- ^ function to convert the AbsPath back to a url</div>
<div>                  -&gt; Application</div><div><br></div><div>We can that function from the higher-level:</div><div><br></div><div>plugToWai :: (WebPlug a) =&gt; (a -&gt; ReaderT (a -&gt; AbsPath) Application) -&gt; Application</div>
<div><br></div><div>if we have a dispatch function that takes argumens:</div><div><br></div><div>fooDispatch :: FilePath -&gt; Int -&gt; a -&gt; ReaderT (a -&gt; AbsPath) Application, we just do something like:</div><div>
<br></div><div>plugToWai (fooDispatch &quot;foo&quot; 1) </div><div><br></div><div>So, to summerize:</div><div><br></div><div> 1. I don&#39;t think dispatch can be a member of the class, because the various dispatch functions may need to take extra arguments, and you can&#39;t do that if dispatch is in a class.</div>
<div> 2. if you pass the mkAbs function via the Reader monad instead of passing it as an explicit argument then you have pretty much exactly reinvented URLT.</div><div><br></div><div>Hence, I think you have no option but to agree that URLT is what you wanted all along ;) I am happy to split the happstack and HSP portions out of the core library. I would even be happy to split regular and TH so that we can have:</div>
<div><br></div><div>urlt</div><div>urlt-regular</div><div>urlt-th</div><div>urlt-mkResource</div><div>urt-hsp</div><div>urlt-happstack</div><div>urlt-wai</div><div>urlt-all</div><div><br></div><div>so that you can only only the extensions you care about.</div>
<div><br></div><div>- jeremy </div></div></div></div>