diff --git a/examples/fact.lhs b/examples/fact.lhs index 0c1ec939..70e74c4f 100644 --- a/examples/fact.lhs +++ b/examples/fact.lhs @@ -1,17 +1,60 @@ +> {-# LANGUAGE QuasiQuotes #-} + I in general recommend type signatures for everything. However, I wanted to show in this example how it is possible to get away without the signatures. +> {-# OPTIONS_GHC -fno-warn-missing-signatures #-} + +There are only two imports: Yesod includes all of the code we need for creating +a web application, while Hack.Handler.SimpleServer allows us to test our +application easily. A Yesod app can in general run on any Hack handler, so this +application is easily convertible to CGI, FastCGI, or even run on the Happstack +server. + +> import Yesod +> import Hack.Handler.SimpleServer + +The easiest way to start writing a Yesod app is to follow the Yesod typeclass. +You define some data type which will contain all the specific settings and data +you want in your application. This might include database connections, +templates, etc. It's entirely up to you. + +For our simple demonstration, we need no extra data, so we simply define Fact +as: + +> data Fact = Fact + +Now we need to declare an instance of Yesod for Fact. The most important +function to declare is handlers, which defines which functions deal with which +resources (aka URLs). + +You can declare the function however you want, but Yesod.Resource declares a +convenient "resources" quasi-quoter which takes YAML content and generates the +function for you. There is a lot of cool stuff to do with representations going +on here, but this is not the appropriate place to discuss it. + + +> instance Yesod Fact where + +The structure is very simply: top level key is a "resource pattern". A resource pattern is simply a bunch of slash-separated strings, called "resource pattern pieces". There are three special ways to start a piece: + +* $: will take any string + +* \#: will take any integer + +* \*: will "slurp" up all the remaining pieces. Useful for something like + /static/*filepath + +Otherwise, the piece is treated as a literal string which must be matched. + + +Now we have a mapping of verbs to handler functions. We could instead simply +specify a single function which handles all verbs. (Note: a verb is just a +request method.) + \begin{code} -{-# OPTIONS_GHC -fno-warn-missing-signatures #-} -{-# LANGUAGE QuasiQuotes #-} - -import Yesod -import Hack.Handler.SimpleServer - -data Fact = Fact -instance Yesod Fact where - handlers = [$resources| + handlers = [$resources| /: Get: index /#num: @@ -19,21 +62,43 @@ instance Yesod Fact where /fact: Get: factRedirect |] - -index = return $ StaticFile TypeHtml "examples/fact.html" -fact i = return $ toHtmlObject - [ ("input", show i) - , ("result", show $ product [1..fromIntegral i :: Integer]) - ] -factRedirect = do - i <- getParam "num" - redirect $ "../" ++ i ++ "/" \end{code} -In particular, the following line would be unnecesary if we had a type -signature here. -\begin{code} - return () -main :: IO () -main = putStrLn "Running..." >> run 3000 (toHackApp Fact) -\end{code} +This does what it looks like: serves a static HTML file. + +> index = return $ StaticFile TypeHtml "examples/fact.html" + +HtmlObject is a funny beast. Basically, it allows multiple representations of +data, all with HTML entities escaped properly. These representations include: + +* Simple HTML document (only recommended for testing). +* JSON (great for Ajax) +* Input to a HStringTemplate (great for no-Javascript fallback). + +For simplicity here, we don't include a template, though it would be trivial to +do so (see the hellotemplate example). + +> fact i = return $ toHtmlObject +> [ ("input", show i) +> , ("result", show $ product [1..fromIntegral i :: Integer]) +> ] + +I've decided to have a redirect instead of serving the some data in two +locations. It fits in more properly with the RESTful principal of one name for +one piece of data. + +> factRedirect = do +> i <- getParam "num" +> redirect $ "../" ++ i ++ "/" + +The following line would be unnecesary if we had a type signature on +factRedirect. + +> return () + +You could replace this main to use any Hack handler you want. For production, +you could use CGI, FastCGI or a more powerful server. Just check out Hackage +for options (any package starting hack-handler- should suffice). + +> main :: IO () +> main = putStrLn "Running..." >> run 3000 (toHackApp Fact)