[bunch of updates to acid-state section and work on new HSP section Jeremy Shaw **20120201221510 Ignore-this: 126ad1124e1998b704f1f08f136baa17 ] hunk ./AcidStateAdvanced.markdown.lhs 261 -
hunk ./AcidStateAdvanced.markdown.lhs 282 -We could integrate it into our app by extend the `Acid` type to hold -the `FooState` and then add a appropriate `HasAcidState` instance: +We could integrate it into our app by extending the `Acid` type to hold +the `FooState` and then add an appropriate `HasAcidState` instance: hunk ./AcidStateAdvanced.markdown.lhs 285 -
hunk ./AcidStateAdvanced.markdown.lhs 313 -
hunk ./AcidStateAdvanced.markdown.lhs 324 -
hunk ./AcidStateAdvanced.markdown.lhs 332 -> withFooPlugin :: (MonadIO m, MonadBaseControl IO m) => FilePath -> (ServerPartT IO Response -> m a) -> m a +> withFooPlugin :: (MonadIO m, MonadBaseControl IO m) => +> FilePath -- ^ path to state directory +> -> (ServerPartT IO Response -> m a) -- ^ function that uses fooPlugin +> -> m a hunk ./AcidStateAdvanced.markdown.lhs 348 +We will come back to this in detail later when we explore plugins and libraries. + addfile ./AcidStateIxSet.lhs hunk ./AcidStateIxSet.lhs 1 +

acid-state counter

+ +

Our first example is a very simple hit counter app.

+ +

First a bunch of LANGUAGE pragmas and imports:

+ +
+ +> {-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, GeneralizedNewtypeDeriving, +> MultiParamTypeClasses, TemplateHaskell, TypeFamilies, RecordWildCards #-} +> +> module Main where +> +> import Control.Applicative ( (<$>) ) +> import Control.Exception ( bracket ) +> import Control.Monad ( msum ) +> import Control.Monad.Reader ( ask ) +> import Control.Monad.State ( get, put ) +> import Data.Data ( Data, Typeable ) +> import Happstack.Server ( Response, ServerPart, dir, nullDir, nullConf, ok +> , simpleHTTP, toResponse ) +> import Data.Acid ( AcidState, Query, Update, makeAcidic, openLocalState ) +> import Data.Acid.Advanced ( query', update' ) +> import Data.Acid.Local ( createCheckpointAndClose ) +> import Data.SafeCopy ( base, deriveSafeCopy ) + +
+ +

Next we define a type that we wish to store in our state. In this case we just create a simple record with a single field count:

+ +
+ +> data CounterState = CounterState { count :: Integer } +> deriving (Eq, Ord, Read, Show, Data, Typeable) +> +> $(deriveSafeCopy 0 'base ''CounterState) +> + +
+ +

deriveSafeCopy creates an instance of the SafeCopy class for CounterState. SafeCopy is class for versioned serialization, deserilization, and migration. Since this is the first version of the CounterState type, we give it version number 0 and declare it to be the base type. Later if we change the type, we can change the version number and provide code to migrate the old instances. The migration will happen automatically when the old state is read. For more information on SafeCopy and migration see the haddock docs.

+ +

Next we will define an initial value that is suitable for initializing the CounterState state.

+ +
+ +> initialCounterState :: CounterState +> initialCounterState = CounterState 0 + +
+ +

Now that we have our types, we can define some update and query functions.

+ +

First let's define an update function which increments the count and returns the incremented value:

+ +
+ +> incCountBy :: Integer -> Update CounterState Integer +> incCountBy n = +> do c@CounterState{..} <- get +> let newCount = count + n +> put $ c { count = newCount } +> return newCount +> + +
+ +

In this line:

+ +
+#ifdef HsColour +> c@CounterState{..} <- get +#endif +
+ +

we are using the RecordWildCards extension. The {..} binds all the fields of the record to symbols with the same name. That is why in the next line we can just write count instead of (count c). Using RecordWildCards here is completely optional, but tends to make the code less cluttered, and easier to read.

+ +

Also notice that we are using the get and put functions from MonadState to get and put the ACID state. The Update monad is basically an enchanced version of the State monad. For the moment it is perhaps easiest to just pretend that incCountBy has the type signature:

+ +
+#ifdef HsColour +> incCountBy :: Integer -> State CounterState Integer +#endif +
+ +

And then it becomes clearer that incCountBy is just a simple function in the State monad which updates CounterState and returns an Integer.

+ +

When the incCountBy function is invoked, it will be run in an isolated manner (the 'I' in ACID). That means that you do not need to worry about some other thread modifying the CounterState between the get and the put. It will also be run atomically (the 'A' in ACID), meaning that either the whole function will run, it will not run at all. If the server is killed mid-transaction, the transaction will either be completely applied or not applied at all.

+ +

You may also note that Update (and State) are not instances of the MonadIO class. This means you can not perform IO inside the update. This is by design. In order to ensure Durability and to support replication, events need to be pure. That allows us to be confident that if the event log has to be replayed -- it will result in the same state we had before. + +

We can also define a query which reads the state, and does not update it:

+ +
+ +> peekCount :: Query CounterState Integer +> peekCount = count <$> ask +> + +
+ +

The Query monad is an enhanced version of the Reader monad. So we can pretend that peekCount has the type:

+ +
+#ifdef HsColour +> peekCount :: Reader CounterState Integer +#endif +
+ +

Although we could have just used get in the Update monad, it is better to use the Query monad if you are doing a read-only operation because it will not block other database transactions. It also lets the user calling the function know that the database will not be affected.

+ +

Next we have to turn the update and query functions into acid-state events. This is almost always done by using the template haskell function makeAcidic

+ +
+ +> $(makeAcidic ''CounterState ['incCountBy, 'peekCount]) +> + +
+ +

The makeAcidic function creates a bunch of boilerplate types and type class instances. If you want to see what is happening under the hood, check out the examples here. The examples with names like, HelloWorldNoTH.hs show how to implement the boilerplate by hand. In practice, you will probably never want to or need to do this. But you may find it useful to have a basic understanding of what is happening. You could also use the -ddump-splices flag to ghc to see the auto-generated instances -- but the lack of formatting makes it difficult to read.

+ +

Here we actually call our query and update functions:

+ +
+ +> handlers :: AcidState CounterState -> ServerPart Response +> handlers acid = +> msum [ dir "peek" $ do c <- query' acid PeekCount +> ok $ toResponse $ "peeked at the count and saw: " ++ show c +> , do nullDir +> c <- update' acid (IncCountBy 1) +> ok $ toResponse $ "New count is: " ++ show c +> +> ] +> + +
+ +

Note that we do not call the incCountBy and peekCount functions directly. Instead we invoke them using the update' and query' functions:

+ +
+#ifdef HsColour +> update' :: (UpdateEvent event, MonadIO m) => +> AcidState (EventState event) -- ^ handle to acid-state +> -> event -- ^ update event to execute +> -> m (EventResult event) +> query' :: (QueryEvent event , MonadIO m) => +> AcidState (EventState event) -- ^ handle to acid-state +> -> event -- ^ query event to execute +> -> m (EventResult event) +#endif +
+ +

Thanks to makeAcidic, the functions that we originally defined now have types with the same name, but starting with an uppercase letter:

+ +
+#ifdef HsColour +> data PeekCount = PeekCount +> data IncCountBy = IncCountBy Integer +#endif +
+ +

The arguments to the constructors are the same as the arguments to the original function.

+ +

So now we can decipher the meaning of the type for the update' and query' functions. For example, in this code:

+ +
+#ifdef HsColour +> c <- update' acid (IncCountBy 1) +#endif +
+ +

The event is (IncCountBy 1) which has the type IncCountBy. Since there is an UpdateEvent IncCountBy instance, we can use this event with the update' function. That gives us:

+ +
+#ifdef HsColour +> update' :: (UpdateEvent IncCountBy, MonadIO m) => +> AcidState (EventState IncCountBy) +> -> IncCountBy +> -> m (EventResult IncCountBy) +#endif +
+ +

EventState is a type function. EventState IncCountBy results in the type CounterState. So that reduces to AcidState CounterState. So, we see that we can not accidently call the IncCountBy event against an acid state handle of the wrong type.

+ +

EventResult is also a type function. EventResult IncCountBy is Integer, as we would expect from the type signature for IncCountBy.

+ +

Finally, we have our main function:

+ +
+ +> main :: IO () +> main = +> do bracket (openLocalState initialCounterState) +> (createCheckpointAndClose) +> (\acid -> +> simpleHTTP nullConf (handlers acid)) + +
+ +

openLocalState starts up acid-state and returns an handle. If existing state is found on the disk, it will be automatically restored and used. If no pre-existing state is found, then initialCounterState will be used.

+ +

The shutdown sequence creates a checkpoint when the server exits. This is good practice because it helps the server start faster, and makes migration go more smoothly. Calling createCheckpointAndClose is not critical to data integrity. If the server crashes unexpectedly, it will replay all the logged transactions (Durability). However, it is a good idea to create a checkpoint on close. If you change an existing update event, and then tried to replay old versions of the event, things would probably end poorly. However, restoring from a checkpoint does not require the old events to be replayed. Hence, always creating a checkpoint on shutdown makes it easier to upgrade the server.

+ hunk ./FileServing.lhs 30 -

Next: Happstack State

+

Next: Web Routes

+ hunk ./IxSet.markdown.lhs 272 -`postById` is used to lookup a specific post by its `PostId`. This is our first example of querying an `IxSet`. He we use the equals query operator: +`postById` is used to lookup a specific post by its `PostId`. This is our first example of querying an `IxSet`. Here we use the equals query operator: hunk ./IxSetDataLens.markdown.lhs 21 - +> +> import Control.Applicative (pure) hunk ./IxSetDataLens.markdown.lhs 24 +> import Control.Comonad.Trans.Store.Lazy hunk ./IxSetDataLens.markdown.lhs 28 -> import Data.Lens (Lens, (^$), (^.), (^=), (^%=), (^%%=), (^+=), (%=)) +> import Data.Lens (Lens, (^$), (^.), (^=), (^%=), (^%%=), (^+=), (%=), getL, setL, modL) hunk ./IxSetDataLens.markdown.lhs 31 +> import Data.Lens.Partial.Common (PartialLens(..), maybeLens, totalLens) hunk ./IxSetDataLens.markdown.lhs 97 -There are two getter operators: one for left-to-right composition and one for right-to-left composition. +There are two getter operators: one for left-to-right composition and one for right-to-left composition. There is also a getter function. They all serve the same purpose -- it's just a matter of taste which you use. hunk ./IxSetDataLens.markdown.lhs 99 -The first one is: +The first operator is: hunk ./IxSetDataLens.markdown.lhs 116 -Notice that `^$` is used a lot like `$`. In fact we could write this with out lenses at all: +Notice that `^$` is used a lot like `$`. In fact we could write this with out lenses at all like this: hunk ./IxSetDataLens.markdown.lhs 152 +Finally, we have the `getL` function: + +
+#ifdef HsColour +> getL :: Lens a b -> a -> b +#endif +
+ +`getL` is useful for creating partially applied functions. For +example, we can create a function that gets a `User`'s first name like +this: + +
+ +> getFirstName :: User -> Text +> getFirstName = getL firstName . getL name + +
+ hunk ./IxSetDataLens.markdown.lhs 245 -> setUserId' :: User -> setUserId' = userId ^= (UserId 1) $ stepcut +> setStepcutUserId :: User +> setStepcutUserId = userId ^= (UserId 1) $ stepcut + +
+ +Instead of the infix operator we could instead `setL`: + +
+#ifdef HsColour +> setL :: Lens a b -> b -> a -> a +#endif +
+ +as such: + +
+ +> setUserId' :: (User -> User) +> setUserId' = setL userId (UserId 1) hunk ./IxSetDataLens.markdown.lhs 287 +Or we could use the `modL` function: + +
+#ifdef HsColour +> modL :: Lens a b -> (b -> b) -> a -> a +#endif +
+ +
+ +> incUserId' :: UserId -> UserId +> incUserId' = modL userInt succ + +
+ hunk ./IxSetDataLens.markdown.lhs 497 +

Using `partial-lens` with IxSet

+ +The `partial-lens` package attempts to address the `fmap` problem that we saw in the last section. A `partial-lens` is similar to a `lens` but allows for the fact that the lens may not always be able to produce a value: + +
+#ifdef HsColour +> newtype PartialLens a b = PLens (a -> Maybe (Store b a)) +#endif +
+ +However, it seems a bit awkward to use `partial-lens` at the moment. To use a normal lens with need to convert it to a partial lens using `totalLens`: + +
+#ifdef HsColour +> totalLens :: Lens a b -> PartialLens a b +#endif +
+ +Additionally, `partial-lens` lacks the `MonadState` interaction that we will examine in the next section (aka, `partial-lens-fd`). But, hopefully these issues will be resolved in the future. + +We can turn our `ixLens` into a partial lens like this: + +
+ +> -- | note: `setPL` does not insert into an `IxSet` it only modifies a +> -- value if the key already exists in the map +> ixPLens :: (Typeable key, Ord a, Typeable a, Indexable a) => key -> PartialLens (IxSet a) a +> ixPLens key = maybeLens . totalLens (ixLens key) + +
+ +See the haddock page for `partial-lens` for more information. Using `partial-lens` is very similar to a normal lens. + hunk ./Makefile 16 -TEMPLATES_DEMOS := HelloBlaze.lhs TemplatesHeist.lhs JMacro.lhs +TEMPLATES_DEMOS := HelloBlaze.lhs TemplatesHeist.lhs TemplatesHSP.lhs JMacro.lhs hunk ./Templates.lhs 16 -

Templating and HTML

+

Templating for HTML and Javascript

hunk ./Templates.lhs 77 +#include "TemplatesHSP.lhs" addfile ./TemplatesHSP.markdown.lhs hunk ./TemplatesHSP.markdown.lhs 1 + + +

Using HSP

+

To enable HSP support, you must install the happstack-hsp package.

+ +HSP is an XML-based templating system that allows you to embed XML in your Haskell source files. If you have ever had to use PHP, you may want to run screaming from this idea. However, the HSP solution is far saner than the PHP solution, so you may want to give it a chance. + +The first thing we will see is a funny `OPTIONS_GHC` pragma at the top of our file: + +
+ +> {-# LANGUAGE FlexibleContexts, OverlappingInstances #-} +> {-# OPTIONS_GHC -F -pgmFtrhsx #-} +> module Main where +> + +
+ +HSP works by running the code through an external pre-processor named `trhsx`. This pragma at the top is how we tell GHC that this file needs to be run through the `trhsx` pre-processor in order to work. + +Next we have some imports: + +
+ +> import Control.Applicative ((<$>)) +> import Control.Monad.Identity (Identity(runIdentity)) +> import qualified HSX.XMLGenerator as HSX +> import Happstack.Server.HSP.HTML +> import Happstack.Server (Request(rqMethod), ServerPartT, askRq, nullConf, simpleHTTP) +> import HSP.Identity () -- instance (XMLGen Identity) +> + +
+ +Now we can define a function which generates an HTML page: + +
+ +> hello :: ServerPartT IO XML +> hello = unXMLGenT +> +> +> Hello, HSP! +> +> +>

Hello HSP!

+>

We can insert Haskell expression such as this: <% sum [1 .. (10 :: Int)] %>

+>

We can use the ServerPartT monad too. Your request method was: <% getMethod %>

+>
+>

We don't have to escape & or >. Isn't that nice?

+>

If we want <% "<" %> then we have to do something funny.

+>

But we don't have to worry about escaping <% "

a string like this

" %>

+>

We can also nest <% like <% "this." %> %>

+> +> +> where +> getMethod :: XMLGenT (ServerPartT IO) String +> getMethod = show . rqMethod <$> askRq +> + +> main :: IO () +> main = simpleHTTP nullConf $ hello +> + +
+ +The first thing we notice is that syntax looks pretty much like normal HTML syntax. There are a few key differences though: + + 1. like XML, all tags must be closed + 2. like XML, we can use shortags (e.g. < hr />) + 3. We do not have to escape & and > + 4. To embed < we have to do something extra funny + +The syntax: + +
+<% haskell expression %> +
+ +allows us to embed a Haskell expression inside of literal XML. + +As shown in this line: + +
+#ifdef HsColour +>

We can also nest <% like <% "this." %> %>

+#endif +
+ +we can freely nest Haskell and XML expressions. + +

What does trhsx do?

+ +In order to use HSP it is very useful to understand what is actually +going on behind the magic. If we have the line: + +
+#ifdef HsColour +> foo :: XMLGenT (ServerPartT IO) XML +> foo = foo +#endif +
+ +and we run `trhsx`, it gets turned into a line like this: + +
+ +> foo :: XMLGenT (ServerPartT IO) XML +> foo = genElement (Nothing, "span") [ asAttr ("class" := "bar") ] [asChild ("foo")] +> + +
+ +We see that the XML syntax has simply been translated into normal haskell function calls. + +

Important HSX types and classes

+ +There are a few types and classes that you will need to be familiar with. + +

the XMLGenT type

+ +The first type is the `XMLGenT` monad transformer: + +
+#ifdef HsColour +> newtype XMLGenT m a = XMLGenT (m a) + +> -- | un-lift. +> unXMLGenT :: XMLGenT m a -> m a +> unXMLGenT (XMLGenT ma) = ma + +#endif +
+ +This seemingly useless type exists solely to make the type-checker happy. Without it we would need an instance like: + +
+#ifdef HsColour +> instance (EmbedAsChild (IdentityT m) a, Functor m, Monad m, m ~ n) => +> EmbedAsChild (IdentityT m) (n a) where +> asChild = ... +#endif +
+ +Unfortunately, because `(n a)` is so vague, that results in overlapping instances that cannot be resolved without `IncohorentInstances`. And, in my experience, enabling `IncohorentInstances` is *never* the right solution. + +So, when generating XML you will generally need to apply `unXMLGenT` to the result to remove the `XMLGenT` wrapper as we did in the `hello` function. Anyone who can figure out to do away with the `XMLGenT` class will be my personal hero. + +

the XMLGen class

+ +Next we have the `XMLGen` class: + +
+#ifdef HsColour +> class Monad m => XMLGen m where +> type XML m +> data Child m +> data Attribute m +> genElement :: Name -> [XMLGenT m [Attribute m]] -> [XMLGenT m [Child m]] -> XMLGenT m (XML m) +> genEElement :: Name -> [XMLGenT m [Attribute m]] -> XMLGenT m (XML m) +> genEElement n ats = genElement n ats [] +> xmlToChild :: XML m -> Child m +> pcdataToChild :: String -> Child m +#endif +
+ +Most of these functions and types are used internally and not used directly by the developer. + +You will notice that we have a type-class instead of just simple functions and types. One feature of HSX is that it is not tied to any particular XML representation. Instead, the XML representation is based on the monad we are currently inside. For example, inside of a javascript monad, we might generate javascript code that renders the XML, inside of another monad, we might generate the `Node` type used by the `heist` template library. We will see some examples of this in a later section. + +The `data` and `type` declarations appearing inside the class declaration are allowed because of the `TypeFamalies` extension. For a detailed coverage of type famalies see this wiki entry. + +

the XML m type synonym

+ +The `XMLGen` type-class defines an associated type synonym `XML m`: + +
+#ifdef HsColour +> type XML m +#endif +
+ +`XML m` is a synoynm for whatever the xml type is for the monad `m`. We can write an XML fragment that is parameterized over an arbitrary monad and xml type like this: + +
+ +> bar :: (XMLGenerator m) => XMLGenT m (HSX.XML m) +> bar = bar +> + +
+ +Note that we had this qualified import: + +
+#ifdef HsColour +> import qualified HSX.XMLGenerator as HSX +#endif +
+ +That is because we need to differentiate the `XML` associated type synonym from the plain-old `XML` data type that is declared elsewhere. Having two types with the same name is a bit silly, but that is the way it is for now. + +

the EmbedAsChild class

+ +The `EmbedAsChild` is used to turn a value into a list of children of an element: + +
+#ifdef HsColour +> type GenChildList m = XMLGenT m [Child m] +> +> -- | Embed values as child nodes of an XML element. The parent type will be clear +> -- from the context so it is not mentioned. +> class XMLGen m => EmbedAsChild m c where +> asChild :: c -> GenChildList m +#endif +
+ +There are generally many instances of `EmbedAsChild` allowing you to embed `String`, `Text`, `Int`, and other values. You might find it useful to create additional instances for types in your program. We will some some examples later in this tutorial. + +To use the `EmbedAsChild` class we us the `<% %>` syntax shown earlier. For example, when we write: + +
+ +> a :: (XMLGenerator m) => GenChildList m +> a = <% 'a' %> +> + +
+ +It gets turned into: + +
+#ifdef HsColour +> a :: (XMLGenerator m) => GenChildList m +> a = (asChild ('a')) +> +#endif +
+ +

the EmbedAsAttr class

+ +The `EmbedAsAttr` class is similar to the `EmbedAsChild` class. It is used to turn arbitrary values into element attributes. + +
+#ifdef HsColour +> type GenAttributeList m = XMLGenT m [Attribute m] +> +> -- | Similarly embed values as attributes of an XML element. +> class XMLGen m => EmbedAsAttr m a where +> asAttr :: a -> GenAttributeList m +#endif +
+ +If we have some attributes like this: + +
+#ifdef HsColour +> foo = foo +#endif +
+ +It will get translated to: + +
+#ifdef HsColour +> foo +> = (genElement (Nothing, "span") +> [asAttr ("class" := "foo"), asAttr ("size" := (80 :: Int)), +> asAttr ("bogus" := False)] +> [asChild ("foo")]) +#endif +
+ +which might be rendered as: + +
+<span class="foo" size="80" bogus="false" +>foo</span +> +
+ +

the XMLGenerator class

+ +You may have noticed that some of the examples had a class constraint `(XMLGenerator m)`: + +
+#ifdef HsColour +> bar :: (XMLGenerator m) => XMLGenT m (HSX.XML m) +> bar = bar +> +#endif +
+ +`XMLGenerator` is just a class alias. It is defined as such: + +
+#ifdef HsColour +> class ( XMLGen m +> , SetAttr m (HSX.XML m) +> , AppendChild m (HSX.XML m) +> , EmbedAsChild m (HSX.XML m) +> , EmbedAsChild m [HSX.XML m] +> , EmbedAsChild m String +> , EmbedAsChild m Char +> , EmbedAsAttr m (Attr String String) +> , EmbedAsAttr m (Attr String Int) +> , EmbedAsAttr m (Attr String Bool) +> ) => XMLGenerator m +#endif +
+ +It contains a list of common instances that all xml generation monads are expected to provide. It just saves you from having to list all thoses instances by hand when you use them. + +

HSX by Example

+ +First we have a simple function to render the pages and print them to stdout: + +
+ +> printXML :: Identity XML -> IO () +> printXML = putStrLn . renderAsHTML . runIdentity + +
+ +

HSX and do syntax

+ +It is possible to use hsx markup inside a `do`-block. You just need to be aware of one little catch. In this example: + +
+#ifdef HsColour +> doBlock :: (XMLGenerator m) => XMLGenT m (HSX.XML m) +> doBlock = +> do
+>

A child element

+>
+#endif +
+ +Notice that we indent the closing </div> tag. That indentation rule is consistent with the specification for how do-notation works. It is intend for the same reason that `if .. then .. else ..' blocks have to be idented in a special way inside `do`-blocks. + +

defaultTemplate

+ +There is a bit of boiler plate that appears in ever html document such as the <html>, <head>, <title>, and <body> tags. The `defaultTemplate` function provides a minial skeleton template with those tags: + +
+#ifdef HsColour +> defaultTemplate :: ( XMLGenerator m +> , EmbedAsChild m body +> , EmbedAsChild m headers +> ) => +> String -- string to put in +> -> headers -- additional elements to put in <head> +> -> body -- elements to put <body> +> -> m (HSX.XML m) +#endif +</div> + +<h4>How to embed empty/nothing/zero</h4> + +`defaultTemplate` requires that we pass in `headers` and a `body`. But what if we don't have any headers that we want to add? + +Most `XMLGenerator` monads provide an `EmbedAsChild m ()` instance, such as this one: + +<div class="code"> +#ifdef HsColour +> instance EmbedAsChild Identity () where +> asChild () = return [] +#endif +</div> + +So, we can just pass in `()` like so: + +<div class="code"> + +> empty = printXML $ defaultTemplate "empty" () () + +</div> + +Which will render as such: + +<div class="code"> +<pre> +<html +><head + ><title + >empty</title + ></head + ><body + ></body + ></html +> +</pre> +</div> + + +<h4>Creating a list of children</h4> + +Sometimes we want to create a number of children elements without knowing what their parent element will be. We can do that using the: + +<div class="code"> + +<%> ... </%> + +</div> + +syntax. For example, here we return two paragraphs: + +<div class="code"> + +> twoParagraphs :: (XMLGenerator m) => XMLGenT m [HSX.Child m] +> twoParagraphs = +> <%> +> <p>Paragraph one</p> +> <p>Paragraph two</p> +> </%> + +</div> + +We can embed those in parent element like this: + +<div class="code"> + +> twoParagraphsWithParent :: (XMLGenerator m) => XMLGenT m (HSX.XML m) +> twoParagraphsWithParent = +> <div> +> <% twoParagraphs %> +> </div> + +</div> + +<h4><code>if .. then .. else .. </code></h4> + +Using an `if .. then .. else ..` is straight-foward. But what happens +when you don't really want an `else` case? This is another place we +can use `()`: + +<div class="code"> + +> ifThen bool = +> printXML $ defaultTemplate "ifThen" () $ +> <div> +> <% if bool +> then <% +> <p>Showing this thing.</p> +> %> +> else <% () %> +> %> +> </div> +> + +</div> + +<h4>Lists of attributes & optional attributes</h4> + +Normally attributes are added to an element using the normal html attribute syntax. HSX, has a special extension where the last attribute can be a Haskell expression which returns a list of attributes to add to the element. For example: + +<div class="code"> + +> attrList = +> printXML $ defaultTemplate "attrList" () $ +> <div id="somediv" ["class" := "classy", "title" := "untitled"] > +> </div> +> + +</div> + +The type of the elements of the list can be anything with an `EmbedAsAttr m a` instance. In this case we create a list of `Attr` values: + +<div class="code"> +#ifdef HsColour +> data Attr n a = n := a +#endif +</div> + +We can use this feature to conditionally add attributes using a simple `if .. then .. else ..` statment: + +<div class="code"> + +> optAttrList bool = +> printXML $ defaultTemplate "attrList" () $ +> <div id="somediv" (if bool then ["class" := "classy", "title" := "untitled"] else []) > +> </div> +> + +</div> + +<h3>web-routes</h3> + +<h3>digestive-functors</h3> + addfile ./TemplatesHSPI18n.markdown.lhs hunk ./TemplatesHSPI18n.markdown.lhs 1 +> {-# LANGUAGE FlexibleInstances, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-} +> {-# OPTIONS_GHC -F -pgmFtrhsx #-} +> module Main where + +> import Control.Applicative ((<$>), optional) +> import Control.Arrow (first, second) +> import Control.Monad (MonadPlus) +> import Control.Monad.Reader (ReaderT, ask, runReaderT) +> import Control.Monad.Trans (MonadIO(liftIO)) +> import Data.ByteString.Char8 (unpack) +> import Data.Function (on) +> import Data.List (sortBy) +> import Data.Maybe (fromMaybe) +> import Data.Text (Text) +> import qualified Data.Text as Text +> import Happstack.Server (Happstack, ServerPartT, getHeaderM, mapServerPartT, nullConf, nullDir, simpleHTTP) +> import Happstack.Server.HSP.HTML +> import Happstack.Server.Internal.Compression (encodings) +> import Text.Shakespeare.I18N (Lang, mkMessage, renderMessage) +> import Text.ParserCombinators.Parsec (parse) + +> type I18N = ServerPartT (ReaderT [Lang] IO) + +> data Msg + +> mkMessage "Msg" "messages" "en" + +> instance EmbedAsChild I18N MsgMessage where +> asChild msg = +> do lang <- ask +> asChild $ renderMessage (undefined :: Msg) lang msg + +> foo :: XMLGenT I18N XML +> foo = <p><% MsgHello %></p> + +> routes :: I18N XML +> routes = +> do nullDir +> defaultTemplate "home" () <p><% MsgHello %></p> + +> main = simpleHTTP nullConf $ withI18n routes + +> -- | parse the 'Accept-Language' header, or [] if not found. +> acceptLanguage :: (Happstack m) => m [(Text, Maybe Double)] +> acceptLanguage = +> do mAcceptLanguage <- (fmap unpack) <$> getHeaderM "Accept-Language" +> case mAcceptLanguage of +> Nothing -> return [] +> (Just al) -> +> case parse encodings al al of +> (Left _) -> return [] +> (Right encs) -> return (map (first Text.pack) encs) + +> mostAcceptable :: [(Text, Maybe Double)] -> [Text] +> mostAcceptable range = +> map fst $ +> filter (\(lang, q) -> lang /= "*" && q > 0) $ +> sortBy (flip compare `on` snd) $ map (second $ fromMaybe 1) range + +> withI18n :: (Functor m, Monad m, MonadPlus m, MonadIO m) => ServerPartT (ReaderT [Lang] m) a -> ServerPartT m a +> withI18n part = +> do langs <- mostAcceptable <$> acceptLanguage +> liftIO $ print langs +> mapServerPartT (flip runReaderT langs) part + hunk ./WebRoutes.lhs 45 -<p><a href="index.html">Back to Table of Contents</a></p> +<p><a href="AcidState.html">Next: Acid State</a></p> +