a classic game implemented using Happstack+Fay+Acid-State (fork of stepcut's mastermind)
root
Happstack, Fay, & Acid-State: Shared Datatypes are Awesome
Haskell is slowly moving onto the browser -- and that is very exciting. We have Fay, GHCJS, UHCJS, Haskell-like languages such as Elm, and more!
In this post I want to demonstrate two ways in which this is awesome.
we can now define a data-type once and use it everywhere
we can now define a type-checked AJAX interface between the browser and the server.
In this post we will be using:
happstack-server
- a modern Haskell based web application serveracid-state
- a native Haskell database systemfay
- a compiler which compiles a subset of Haskell to Javascript
The Bad Old Days
Let's first consider a more traditional system where we use happstack-server
, a SQL database, and Javascript. If we have a value we want to store in the database, manipulate on the server, and send to the client, we need to manually create several representations of this value and manually write code to marshal to and from the various representations.
In the SQL database, the value needs to be represented via columns and relations in one or more tables. On the server-side we need to create an algebraic data type to represent the value. To transmit the value to the client we need to the convert the value into a JSON object. And then on the client-side we may then need to convert the JSON value into a Javascript object.
To switch between each of these representations we need to manually write code. For example, we need to write the SQL statements to update and retrieve the value from the database.
So in addition to the four representations of our data, we have 3 bidirectional conversions to manage as well:
SQL <=> ADT <=> JSON <=> JAVASCRIPT
Now let's say we need to make a change to our datatype -- we have to correctly update 10 different aspects of our code.
Because SQL and Javascript are outside of the domain of Haskell, we don't even get the help of the typechecker to make sure we have keep all the types in-sync.
A popular mantra in computer programming is DRY
- "Don't repeat yourself". Yet, here we have to repeat ourselves 10 times!
In addition to keeping everything in sync, we still have the problem of having to think about the same data in 4 different ways:
- as relational data
- as an algebraic data type
- as a JSON object
- as a Javascript object
The Path to Awesome
The picture when using happstack-server
, acid-state
, and fay
is radically different. In this system we define our data type as a nice algebraic data type which can be stored in acid-state, manipulated on the server, and sent to the client, where it is also treated as the same ADT. This definition occurs in once in a normal Haskell file that is shared by all three pieces of the system.
The data does still need to be serialized by acid-state
and for communication to/from the client (via AJAX), however, this serialization is done entirely automatically via template haskell and generics.
mastermind
I have created a simple example of using happstack-server
, acid-state
, and fay
to implement an interactive web 2.0 mastermind clone. The board updates all occur client side and communication is done over a typed AJAX communication channel.
You can find all the source code here:
http://hub.darcs.net/stepcut/mastermind
A demonstration of the game play is shown is this video:
http://www.youtube.com/watch?v=K2jdUlhX_E8
There are some bugs in the code, unimplemented features, etc. It seems to display correctly in Chrome, but not Mastermind. If any of these things bother you, feel free to submit patches. These issues, do not get in the way of the interesting things we want to demonstrate, and so they will likely remain unfixed.
The tree is organized as follows:
MasterMind.Client.*
- client-side Fay codeMasterMind.Server.*
- server-side Haskell codeMasterMind.Shared.*
- code that is shared between the client and server
shared types
MasterMind.Shared.Core
contains the datatypes needed to define the state of the game board. There is not much to say about these types -- they are basically what you would expect to see for a game like mastermind.
In MasterMind.Server.Acid
those types are stored persistently using acid-state
. All played games are retained in the database, though there is currently no code implemented to browse them.
In MasterMind.Client.Main
those same types (such as Color
, Guess
, and Board
) are imported and used for the client-side interactions.
By virtue of the fact that everything fits together so seamlessly -- there isn't much to say. It looks like we just defined some normal Haskell datatypes and used them in normal Haskell code -- just like any other Haskell program. The interesting part is really what is missing! We've managed to eliminate all that manual conversion, having to think about multiple representation of the same data, javascript, SQL, etc, and left ourselves with nice, simple Haskell code! When we want to change the type, we just change the type in one place. If we need to update code, the type-checker will complain and let us know!
Best of all, we do not need to rely on special syntax introduced via QuasiQuotation. We define the types using normal Haskell data declarations.
There is a bit of Template Haskell code in the acid-state
portions of the code. To create SafeCopy
instances we use deriveSafeCopy
. In principle this is not much different from the standard deriving Data, Typeable
mechanism. However, for those that eschew Template Haskell, there is work on allowing SafeCopy
to use the new Generics
features in GHC 7.2.
There is also a Template Haskell function makeAcidic
which would be a bit more difficult to remove.
The typed AJAX interface
Now that we have a way to share types between the client and server, it is relatively straight-forward to use those types to build a type-safe communication channel between the client and server.
At the end of MasterMind.Shared.Core
there is a type:
> data Command
> = SendGuess Guess (ResponseType (Maybe Row))
> | FetchBoard (ResponseType (Maybe Board))
> deriving (Read, Show, Data, Typeable)
The Command
type defines the AJAX interface between the server and the client. The constructors 'SendGuess' and 'FetchBoard' are commands that the client wants to send, and the ResponseType a
is what the server will return.
It would be far more sensible to declare Command
as a GADT
:
> data Command r where
> SendGuess :: Guess -> Command (Maybe Row)
> FetchBoard :: Command (Maybe Board)
Unfortuantely, Fay does not support GADTs
at this time, so we have to use a series of hacks to get the type safety we are hoping for. Language.Fay.AJAX
(from happstack-fay
) defines a type:
> data ResponseType a = ResponseType
This gives us a phantom type variable that we can use to encode the type of the response.
Looking at the Command
type again, you will see that the last argument to every constructor is a ResponseType
value:
> data Command
> = SendGuess Guess (ResponseType (Maybe Row))
> | FetchBoard (ResponseType (Maybe Board))
> deriving (Read, Show, Data, Typeable)
On the client-side we can use call
to send an AJAX command:
> call :: (Foreign cmd, Foreign res) =>
> String -- ^ URL to @POST@ AJAX request to
> -> (ResponseType res -> cmd) -- ^ AJAX command to send to server
> -> (res -> Fay ()) -- ^ callback function to handle response
> -> Fay ()
For example:
> call "/ajax" (SendGuess guess) $ \mRow ->
> case mRow of
> Nothing -> alert "Invalid game id"
> (Just row) -> updateBoard row
You will note that the type signature for call
is a bit funny. The type for the cmd
argument is:
> (ResponseType res -> cmd)
instead of just
> cmd
But on closer examination, we see that is how the type-checker is able to enforce that command and response handler types match. When we actually use call
we just leave off the last argument to the constructor, and the code is quite readable.
Also, note that call
is asynchronous -- meaning that call
we return immediately, and the handler will be called after the server sends back a response. That is why we pass in a callback function instead of just doing:
> mRow <- call "/ajax" (SendGuess guess) -- this is not how it actually works
We could create a synchronous version of call, however the underlying javascript engine is single-threaded and that could result in the UI blocking. We could probably give the appearance of a blocking call
by using continuations in some fashion, but we will consider that another time.
On the server-side we use a pair of functions:
> handleCommand :: (Data cmd, Show cmd, Happstack m) =>
> (cmd -> m Response)
> -> m Response
>
> fayResponse :: (Happstack m, Show a) =>
> ResponseType a
> -> m a
> -> m Response
handleCommand
decodes the AJAX request and passes it to a handler. fayResponse
is used to convert a return value into a valid Fay response. The ResponseType a
parameter is used to enforce type safety. So in the code we are going to have something like this in our top-level route:
> , dir "json" $ handleCommand (commandR acid)
where commandR
looks like:
> commandR :: AcidState Games
> -> Command
> -> ServerPart Response
> commandR acid cmd =
> case cmd of
> (SendGuess guess rt) -> fayResponse rt $ sendGuessC acid guess
> (FetchBoard rt) -> fayResponse rt $ fetchBoardC acid
We see that we pull the ResponseType
value from the constructor and pass it to fayResponse
, so that the type checker will enforce that constraint.
The command handlers have types like:
> sendGuessC :: AcidState Games > -> Guess > -> ServerPart (Maybe Row)
> fetchBoardC :: AcidState Games > -> ServerPart (Maybe Board)
Hopefully we can add GADTs
to Fay soon, which will remove some of the boilerplate.
Cabal
If we want to use cabal to build and install our web application, then we need to tell cabal how to compile the Fay code to Javscript. I believe the long term plan is for Cabal to somehow directly support Fay packages. But in the meantime, this custom Setup.hs seems to do the trick:
Setup.hs for building Fay code
Note that in mastermind.cabal
we have build-type: Custom
instead of that standard build-type: Simple
. You need to specify Custom
or cabal will ignore the Setup.hs
.
What Still Sucks
Fay is still very raw and buggy. In order to get this simple application working I had to file four bugs against Fay and commit several other patches myself. When the developers say that Fay is still alpha they mean it.
On the other hand, the Fay team was very responsive and fixed my issues quickly!
If you want to experiment with Fay, I highly recommend it -- but be prepared to run into some issues.
Programming in Fay is far nicer than Javascript. But, ultimately we still have to deal with the DOM model that the browser is based around. And, even in Fay, that still sucks (even with bootstrap and jQuery to help). However, now that we have a nice language to work with, we can hopefully create a nice Fay-based library for client-side UI development.
Conclusion
Fay (and friends) are definitely a huge step in the right direction. Things are still just getting started, but they are definitely set to revolution Haskell web programming. We already have mature solutions for web 1.0 programming such as happstack-server
and reform
. But, technologies like Fay are making it far easier to provide web 2.0 solutions with rich client-side functionality.
I have released happstack-fay on hackage which provides the glue code needed for AJAX communication.
In future blog posts I hope to cover three additional topics:
how to incorporate type-safe routing using
web-routes
how to add client-side validation to reform using Fay
how to use HSX for client-side HTML generation
We would love to hear your feedback!