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 JavascriptLet'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:
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.
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 Firefox (and possibly others). 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 serverMasterMind.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.
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.
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
.
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.
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!