[added Template Haskell intro and try to clarify some ideas in acid-state Jeremy Shaw **20120404153117 Ignore-this: b252ddbc0395f1089a154638f89d978a ] hunk ./AcidState.lhs 24 +

How acid-state works

+ +

A very simple way to model a database in Haskell would be to create a datatype to represent your data and then store that data in a mutable, global variable, such as a global IORef. Then you could just write normal Haskell functions to query that value and update it. No need to learn a special query language. No need to marshal your types from expressive Haskell datatypes to some limited set of types supported by an external database.

+ +

That works great.. as long as your application is only single-threaded, and as long as it never crashes, and never needs to be restarted. But, for a web application, those requires are completely unacceptable. The idea is still appealing though. acid-state provides a practical implementation of that idea which actually implements the ACID guarantees that you may be familiar with from traditional relational databases such as MySQL, postgres, etc.

+ +

In acid-state we start by defining a type that represents the state we wish to store. Then we write a bunch of pure functions that query that value or which return an updated value. However, we do not call those functions directly. Instead we keep the value inside an AcidState handle, and we call our functions indirectly by using the update and query functions. This allows acid-state to transparently log update events to disk, to ensure that update and query events run automatically and in isolation, etc. It is allows us to make remote API calls, and, eventually, replication and multimaster.

+ +

Note that when we say acid-state is pure, we are referring specifically to the fact that the functions we write to perform updates and queries are pure. acid-state itself must do IO in order to coordinate events from multiple threads, log events to disk, perform remote queries, etc.

+ +

Now that you have a vague idea how acid-state works, let's clarify it by looking at some examples.

+ hunk ./AcidState.lhs 41 +

Next: Template Haskell

+ hunk ./AcidStateCounter.lhs 41 -

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.

+

deriveSafeCopy creates an instance of the SafeCopy class for CounterState. SafeCopy is class for versioned serialization, deserilization, and migration. The SafeCopy class is a bit like a combination of the Read and Show classes, except that it converts the data to a compact ByteString representation, and it includes version information in case the type changes and old data needs to be migrated.

+ +

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 will increment the version to 1 and declare it to be an extension of a previous type. We will also provide a migration instance to migrate the old type to the new type. The migration will happen automatically when the old state is read. For more information on SafeCopy, base, extension and migration see the haddock docs. (A detailed section on migration for the Crash Course is planned, but not yet written).

+ +

If you are not familiar with Template Haskell be sure to read this brief intro to Template Haskell

hunk ./AcidStateCounter.lhs 92 -

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 or 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.

+

Note that even though we are using a monad here.. the code is still pure. If we wanted we could have required the update function to have a type like this instead:

+ +
+#ifdef HsColour +> incCountBy :: Integer -> CounterState -> (CounterState, Integer) +#endif +
+ +

In that version, the current state is explicitly passed in, and the function explicitly returns the updated state. The monadic version does the same thing, but uses >>= to make the plumbing easier. This makes the monadic version easier to read and reduces mistakes.

+ +

When we later use the update function to call incCountBy, incCountBy 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 or 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.

hunk ./AcidStateCounter.lhs 142 -> handlers acid = +> handlers acid = hunk ./AcidStateCounter.lhs 145 -> , do nullDir +> , do nullDir hunk ./AcidStateCounter.lhs 158 -> update' :: (UpdateEvent event, MonadIO m) => -> AcidState (EventState event) -- ^ handle to acid-state +> update' :: (UpdateEvent event, MonadIO m) => +> AcidState (EventState event) -- ^ handle to acid-state hunk ./AcidStateCounter.lhs 162 -> query' :: (QueryEvent event , MonadIO m) => -> AcidState (EventState event) -- ^ handle to acid-state +> query' :: (QueryEvent event , MonadIO m) => +> AcidState (EventState event) -- ^ handle to acid-state hunk ./AcidStateCounter.lhs 192 -> update' :: (UpdateEvent IncCountBy, MonadIO m) => -> AcidState (EventState IncCountBy) -> -> IncCountBy +> update' :: (UpdateEvent IncCountBy, MonadIO m) => +> AcidState (EventState IncCountBy) +> -> IncCountBy hunk ./AcidStateCounter.lhs 203 +

As mentioned earlier, the underlying update and query events we created are pure functions. But, in order to have a durable database (aka, be able to recover after powerloss, etc) we do need to log these pure events to disk so that we can reply them in the event of a recovery. So, rather than invoke our update and query events directly, we call them indirectly via the update and query functions. update and query interact with the acid-state system to ensure that the acid-state events are properly logged, called in the correct order, run atomitically and isolated, etc.

+ +

There is no way in Haskell to save a function to save a function to disk or send it over the network. So, acid-state has to cheat a little. Instead of storing the function, it just stores the name of the function and the value of its arguments. That is what the IncCountBy type is for -- it is the value that can be serialized and saved to disk or sent over the network.

+ hunk ./AcidStateCounter.lhs 213 -> do bracket (openLocalState initialCounterState) +> do bracket (openLocalState initialCounterState) hunk ./AcidStateCounter.lhs 220 -

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.

+

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. openLocalState stores data in a directory named state/[typeOf state]. In this example, that would be, state/CounterState. If you want control over where the state information is stored use openLocalStateFrom instead.

hunk ./HelloWorld.lhs 64 -

The top-level function simpleHTTP is what actually starts the programming listening for incoming HTTP requests.

+

The top-level function simpleHTTP is what actually starts the program listening for incoming HTTP requests.

hunk ./Makefile 4 -MAIN := Main.lhs -DEPS := HelloWorld.lhs RouteFilters.lhs Templates.lhs RqData.lhs FileServing.lhs WebRoutes.lhs AcidState.lhs +MAIN := Main.lhs +DEPS := HelloWorld.lhs RouteFilters.lhs Templates.lhs RqData.lhs FileServing.lhs WebRoutes.lhs AcidState.lhs TemplateHaskell.lhs hunk ./Makefile 7 -ROUTE_FILTER_DEMOS := MonadPlus.lhs Dir.lhs Dir2.lhs Dirs.lhs Path.lhs FromReqURI.lhs MethodM.lhs MatchMethod.lhs +ROUTE_FILTER_DEMOS := MonadPlus.lhs Dir.lhs Dir2.lhs Dirs.lhs Path.lhs FromReqURI.lhs MethodM.lhs MatchMethod.lhs hunk ./Makefile 16 -TEMPLATES_DEMOS := HelloBlaze.lhs TemplatesHeist.lhs TemplatesHSP.lhs JMacro.lhs TemplatesHSPI18n.lhs +TEMPLATES_DEMOS := HelloBlaze.lhs TemplatesHeist.lhs TemplatesHSP.lhs JMacro.lhs TemplatesHSPI18n.lhs hunk ./Makefile 28 -FileServing.html : FileServing.lhs $(FILESERVING_DEMOS) FileServingAdvanced.lhs +FileServing.html : FileServing.lhs $(FILESERVING_DEMOS) FileServingAdvanced.lhs hunk ./Makefile 31 +TemplateHaskell.html : TemplateHaskell.lhs hunk ./Makefile 61 - #tidy -q -o $@ Main.html + #tidy -q -o $@ Main.html addfile ./TemplateHaskell.lhs hunk ./TemplateHaskell.lhs 1 + + + + Crash Course in Happstack + + + + + +

Back to Table of Contents

+

Using Template Haskell

+ + +

Template Haskell is a GHC extension that makes it possible to generate new code at compile time. It is like a more powerful version of C macros, but a bit more restrictive than LISP macros. You can see the code that is being generated by passing the -ddump-splices flag to GHC.

+ +

There are only a few places in Happstack where you will encounter Template Haskell code. In each of those cases, it is used to generate some very boilerplate code. You are already familiar with one code generation mechanism in Haskell -- the deriving (Eq, Ord, Read, Show, Data, Typeable) clause. In Happstack, we use Template Haskell in a similar manner to derive instances of classes like SafeCopy and IsAcidic.

+ +

There are only a few simple things you will need to learn to use Template Haskell with Happstack.

+ +

To enable Template Haskell you will need to include {-# LANGUAGE TemplateHaskell #-} at the top of the file.

+ +

Here is an example of some Template Haskell that derives a SafeCopy instance:

+ +
+#ifdef HsColour +> $(deriveSafeCopy 0 'base ''CounterState) +#endif +
+ +

There are three new pieces of syntax you will see:

+ +
+
$( )
+
This syntax is used to indicate that the code inside is going to generate code. The $(..) will be replaced by the generated code, and then the module will be compiled. The use of $( ) is optional (since GHC 6.12 or so).
+
'
+
The single quote in 'base is syntax that returns the Name of a function or constructor. (Specificially, Language.Haskell.TH.Syntax.Name).
+
''
+
Note: that is two single ticks '' not a double-quote ". It serves the same purpose as ' except that it is used to get the Name of a type instead of a function or constructor.
+
+ +

Finally, you may occasionally run into some staging restrictions. In a normal Haskell source file, it does not matter what order you declare things. You can use a type in a type signature, and then define the type later in the file. However, when using Template Haskell, you may occasionally find that you need to order your types so that they are declared before they are used. If the compiler complains that it can't find a type that you have clearly defined in the file, try moving the declaration up higher.

+ +

That is everything you should need to know to use Template Haskell in Happstack. See the relevant section of the crash course for the details of calling specific Template Haskell functions such as deriveSafeCopy.

+ + + + hunk ./theme.css 2 -@import url(http://fonts.googleapis.com/css?family=Puritan); +/* @import url(http://fonts.googleapis.com/css?family=Puritan); */ hunk ./theme.css 7 -@import url(http://fonts.googleapis.com/css?family=Gudea); hunk ./theme.css 8 +@import url(http://fonts.googleapis.com/css?family=Gudea); + hunk ./theme.css 12 - font-family: 'Puritan', sans-serif; + font-family: 'Gudea', sans-serif;