Haskell for Web Developers
This blog post is somewhat dated and may not reflect
changes to the ecosystem since 2013.
The perpetual myth persists that Haskell cannot be used for "real world
applications". Normally real world is usually left undefined in such a
discussion, but can often be taken to mean that Haskell is not suited
for database and web development work.
Haskell has a rich library ecosystem and is well-suited for these
tasks but I concede that there might be a systemic lack of
introductory material for many domain specific tasks. Something
that many projects and
companies are trying to remedy.
Haskell does indeed have several great web frameworks along the
lines of RoR, Django, Flask, Pyramid etc.
I will not discuss these though because I really couldn't give a
better introduction than their own documentation. Instead I will
focus on simple motivating examples for smaller libraries which
provide a rich feature base for web development tasks while
leveraging the strengths of Haskell language itself, and many of
which can integrate with the larger frameworks.
Clay
Clay is a library for programmatic generation of CSS. Is it an
embedded DSL (EDSL) that exposes selectors and styles for the
CSS3
grammmer.
Clay is designed to layer logic on top of the CSS as to encode
variables, color mixing, complex selector logic and nested rules
more easily than with base CSS. Clay can also be usefull as a
lower-level combinator library to describe complex CSS layouts.
$ cabal install clay
{-# LANGUAGE OverloadedStrings #-}
import Clay
import Data.Text
import Prelude hiding (div)
bodyStyle :: Css
bodyStyle = body ? do
background aquamarine
fontFamily ["Helvetica Neue"] [sansSerif]
codeStyle :: Css
codeStyle = code ?
do fontFamily ["Monaco", "Inconsolata"] [monospace]
fontSize (px 14)
lineHeight (ex 1.8)
emphasis :: Css
emphasis = do
fontWeight bold
color black
textTransform uppercase
container :: Selector
container = div # ".code"
containerStyle :: Css
containerStyle = container ?
do width (px 800)
borderColor gray
main :: IO ()
main = putCss $ do
bodyStyle
codeStyle
containerStyle
The above will generate the following stylesheet:
body {
background: rgb(127, 255, 212);
font-family: "Helvetica Neue", sans-serif;
}
code {
font-family: "Monaco", "Inconsolata", monospace;
font-size: 14px;
line-height: 1.8ex;
}
div.code {
width: 800px;
border-color: rgb(128, 128, 128);
}
Blaze
$ cabal install blaze-html
Blaze is the bread and butter of markup generation in Haskell. It
is described as a "blazingly fast HTML combinator library" which
programmtically generates HTML and several other markup languages
from an embedded DSL.
In this module, the language extension OverloadedStrings
is
used so that the type inferencer can infer common coercions
between String-like types without having to do explicit calls to
boilerplate functions (pack
, unpack
, html
) for each
string-like literal. This will be pretty common use for all the
examples from here out that use ByteString
or HTML.
{-# LANGUAGE OverloadedStrings #-}
import Data.Monoid
import Text.Blaze (ToMarkup(..))
import Text.Blaze.Html5 hiding (html, param)
import Text.Blaze.Html.Renderer.Text (renderHtml)
import qualified Text.Blaze.Html5 as H
gen :: Html -> [Html] -> Html
gen title elts = H.html $ do
H.head $
H.title title
H.body $
H.ul $ mapM_ H.li elts
main :: IO ()
main = do
print $ renderHtml $ gen "My Blog" ["foo", "bar", "fizz"]
This would output HTML like the following:
<html>
<head>
<title>My Blog</title>
</head>
<body>
<ul>
<li>foo</li>
<li>bar</li>
<li>fizz</li>
</ul>
</body>
</html>
In addition to generating HTML we can also derive from it's
internal ToMarkup classes to provide HTML representations for any
datatype in Haskell. A silly example might be:
data Example = A | B deriving (Show)
data List a = Cons a | Nil deriving (Show)
instance ToMarkup Animal where
toMarkup = toHtml . show
instance (ToMarkup a) => ToMarkup (List a) where
toMarkup x = case x of
Cons a -> H.ul $ H.li $ toHtml a
Nil -> ""
<!-- Cons (Cons (Cons A)) -->
<ul>
<li>
<ul>
<li>
<ul>
<li>A</li>
</ul>
</li>
</ul>
</li>
</ul>
It is worth noting that the Blaze builder overloads do-notation
as some EDSLs do, but the Html
type is not a monad. It is
functionally a monoid.
For non-embedded template languages along the lines of Jinja or
erb refer to the Shakespearean
templates
or heist.
JMacro
$ cabal install jmacro
JMacro is quasiquoter for Javascript code generation. The
underlying implementation is rather clever and allows Haskell and
Javascript to share functions and values across quotation
boundaries. The end result is a fusion of Haskell and JavaScript
that serves as a foundation to higher
abstractions
and as a very convienant way to implement code generation for
compilers targetting Javascript.
As an example of we'll use JMacro to implement a simple translator for
the untyped typed lambda calculus, something one might do if writing a
language that transpiles to Javascript.
{-# LANGUAGE QuasiQuotes, TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-}
import Data.String
import Language.Javascript.JMacro
jref :: Sym -> JExpr
jref = ValExpr . JVar . StrI
jvar :: Sym -> JStat
jvar sym = DeclStat (StrI sym) Nothing
jprint x = [jmacroE|console.log(x)|]
instance IsString Expr where
fromString x = Var (Sym x)
data Val = Sym Sym
| Lit Lit
deriving (Show)
type Sym = String
data Lit = LStr String
| LInt Int
deriving (Show)
data Expr = App Expr Expr
| Lam Sym Expr
| Var Val
deriving (Show)
-- Convert Haskell expressions to Javascript expressions
instance ToJExpr Val where
toJExpr (Sym s) = toJExpr s
toJExpr (Lit l) = toJExpr l
instance ToJExpr Lit where
toJExpr = toJExpr
instance ToJExpr Sym where
toJExpr = jref
instance ToJExpr Expr where
toJExpr (Lam s ex) =
[jmacroE|
function(arg) {
`(jvar s)`;
`(jref s)` = `(arg)`;
return `(ex)`;
}
|]
toJExpr (App f x) =
[jmacroE| `(f)`(`(x)`) |]
toJExpr (Var v) =
toJExpr v
compile :: ToJExpr a => a -> String
compile = show . renderJs . toJExpr
s, k, i0, i1 :: Expr
s = Lam "f" $ Lam "g" $ Lam "x" $ (App "f" "x") `App` (App "g" "x")
k = Lam "x" $ Lam "y" "x"
i0 = Lam "x" "x"
i1 = App (App s k) k
main :: IO ()
main = do
putStrLn $ compile s
putStrLn $ compile k
putStrLn $ compile i0
putStrLn $ compile i1
Fay
$ cabal install fay
Fay is a growing ecosystem of packages that can compile Haskell
to Javascript. Fay works with a strict subset of Haskell that
preserves Haskell semantics such as currying and laziness. In
addition to the core language, ther are interfaces for
jquery and DOM
manipulation so that
Fay-compiled Haskell code can effectively access the browser
internals.
$ cabal install fay-dom fay-jquery
The code generation is rather verbose given that it compiles
quite a bit of the Haskell Prelude. The below example is very
simple and only the interesting part of the outputted source is
shown below. Notably the generated code is very readable.
-- demo.hs
import FFI
import Prelude
import JQuery
puts :: String -> Fay ()
puts = ffi "console.log(%1)"
example = take 25 [1..]
main :: Fay ()
main = ready $ do
el <- select "#mydiv"
setCss "background-color" "red" el
puts "Hello World!"
puts (show [1,2,3])
To compile invoke the compiler:
$ fay demo.hs --package fay-jquery
Some of the generated code:
var Prelude$enumFrom = function ($p1) {
return new Fay$$$(function () {
var i = $p1;
return Fay$$_(Fay$$_(Fay$$cons)(i))(
Fay$$_(Prelude$enumFrom)(Fay$$_(Fay$$_(Fay$$add)(i))(1)),
);
});
};
var Main$example = new Fay$$$(function () {
return Fay$$_(Fay$$_(Prelude$take)(25))(Prelude$enumFrom(1));
});
To call this code from vanilla Javascript:
var main = new Main();
main._(main.Main$main);
Fay is part of a larger community of compilers that transpile
functional languages to Javascript. Another library of note is
Roy. Although not Haskell, it has
a sophisticated type system and notably an implementation of
typeclasses, a feature that Fay currently does not implement.
Aeson
$ cabal install aeson
Aeson is the de-facto JSON parsing and generation library for
Haskell. It's usage couldn't be simpler, we simply declare
instance of toJSON
and fromJSON
for our types and Aeson
takes care of the mappings and exception handling. By using
DeriveGeneric
we can create instances with very little code.
{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
import Network.HTTP
import Control.Applicative
import Data.ByteString (ByteString)
data Message = Message {
text :: ByteString
, date :: ByteString
} deriving (Show, Generic)
instance FromJSON Message
instance ToJSON Message
fromStdin :: IO (Either String Message)
fromStdin = eitherDecode <$> readLn
postgres-simple
$ cabal install postgres-simple
Postgres-simple is a library for communicating with Postgres
databases and mapping data between Haskell and SQL types.
Although not an ORM, postgres-simple
lets us generate and
execute SQL queries and map result sets onto our algebraic
datatypes very simply by deriving instances to declare schemas.
{-# LANGUAGE OverloadedStrings #-}
import Data.Text
import Control.Applicative
import Control.Monad
import Database.PostgreSQL.Simple
import Database.PostgreSQL.Simple.FromRow
import qualified Data.ByteString as B
import qualified Database.PostgreSQL.Simple as Pg
data Client = Client { firstName :: Text
, lastName :: Text
, clientLocation :: Location
}
data Location = Location { address :: Text
, location :: Text
}
instance Pg.FromRow Location where
fromRow = Location <$> field <*> field
instance Pg.FromRow Client where
fromRow = Client <$> field <*> field <*> liftM2 Location field field
queryClients :: Connection -> IO [Client]
queryClients c = query_ c "SELECT firstname, lastname, location FROM clients"
main :: IO [Client]
main = do
uri <- B.getLine
conn <- connectPostgreSQL uri
queryClients conn
Acid-State
$ cabal install acid-state
We can further exploit Haskell's algebraic datatypes to give us a
storage engine simply from the specification of our types and a
little bit of TemplateHaskell
usage. For instance, a simple
Map
container from Data.Map
can be transformed to a
disk-backed disk backed key-value store which can be interacted
with as if it were a normal Haksell data structure.
type Key = String
type Value = String
data Database = Database !(Map.Map Key Value)
Like it's name implies acid-state
provides transactional
guarantees for storage. Specifically that writes will be applied
completely or not at all and that data will be consistent during
reads.
{-# LANGUAGE OverloadedStrings, TypeFamilies, DeriveDataTypeable, TemplateHaskell #-}
import Data.Acid
import Data.Typeable
import Data.SafeCopy
import Control.Monad.Reader (ask)
import qualified Data.Map as Map
import qualified Control.Monad.State as S
type Key = String
type Value = String
data Database = Database !(Map.Map Key Value)
deriving (Show, Ord, Eq, Typeable)
$(deriveSafeCopy 0 'base ''Database)
insertKey :: Key -> Value -> Update Database ()
insertKey key value
= do Database m <- S.get
S.put (Database (Map.insert key value m))
lookupKey :: Key -> Query Database (Maybe Value)
lookupKey key
= do Database m <- ask
return (Map.lookup key m)
deleteKey :: Key -> Update Database ()
deleteKey key
= do Database m <- S.get
S.put (Database (Map.delete key m))
allKeys :: Int -> Query Database [(Key, Value)]
allKeys limit
= do Database m <- ask
return $ take limit (Map.toList m)
$(makeAcidic ''Database ['insertKey, 'lookupKey, 'allKeys, 'deleteKey])
fixtures :: Map.Map String String
fixtures = Map.empty
test :: Key -> Value -> IO ()
test key val = do
database <- openLocalStateFrom "db/" (Database fixtures)
result <- update database (InsertKey key val)
result <- query database (AllKeys 10)
print result
Digestive Functors
$ cabal install digestive-functors digestive-functors-blaze
Digestive functors solve the very mundane but mechanical task of
validating forms. The library provides a way to specify views and
validation logic and handle the control flow of validation
between the end-user and the server. There are several backends
to render the form and handle request/response cycles depending
on your choice of framework For arbitrary reasons we'll choose
Happstack for this example.
$ cabal install digestive-functors-happstack
We'll build a simple signup page with username and email validation logic.
{-# LANGUAGE OverloadedStrings #-}
import Data.Maybe
import Text.Printf
import Control.Applicative
import Data.Text (Text, find, splitOn)
import Text.Digestive
import Text.Digestive.Happstack
import Text.Digestive.Blaze.Html5
import qualified Text.Blaze.Html5 as H
import qualified Happstack.Server as HS
data User = User
{ userName :: Text
, userMail :: Text
} deriving (Show)
userForm :: Monad m => Form Text m User
userForm = User
<$> "name" .: check "Name must be two words" checkName (text Nothing)
<*> "email" .: check "Not a valid email address" checkEmail (text Nothing)
checkEmail :: Text -> Bool
checkEmail = isJust . find (== '@')
checkName :: Text -> Bool
checkName s = length (splitOn " " s) == 2
signupView :: View H.Html -> H.Html
signupView view = form view "/" $ do
label "name" view "Full Name:"
inputText "name" view
H.br
label "email" view "Email:"
inputText "email" view
H.br
childErrorList "" view
inputSubmit "Signup"
template :: H.Html -> H.Html
template body = H.docTypeHtml $ do
H.head $ H.title "Example form:"
H.body body
reply m = HS.ok $ HS.toResponse $ template m
page :: HS.ServerPart HS.Response
page = do
HS.decodeBody $ HS.defaultBodyPolicy "/tmp/" 0 40960 40960
r <- runForm "test" userForm
case r of
(view, Nothing) -> do
let view' = fmap H.toHtml view
reply $ form view' "/" (signupView view')
(_, Just response) ->
reply $ do
H.h1 "Form is valid."
H.p $ H.toHtml $ show response
config :: HS.Conf
config = HS.nullConf { HS.port = 5000 }
main :: IO ()
main = do
printf "Listening on port %d\n" (HS.port config)
HS.simpleHTTP config page
Servers
A great deal of effort has been put into making the Haskell
runtime
implement efficient event driven programming such that
applications can take advantage of the Haskell threading support.
A simple single-threaded Hello World might be written like the
following:
{-# LANGUAGE OverloadedStrings #-}
import Network
import Data.ByteString.Char8
import System.IO (hClose, hSetBuffering, Handle, BufferMode(LineBuffering))
msg = "HTTP/1.0 200 OK\r\nContent-Length: 12\r\n\r\nHello World!\r\n"
handleClient :: Handle -> IO ()
handleClient handle = do
hSetBuffering handle LineBuffering
hGetLine handle
hPutStrLn handle msg
hClose handle
listenLoop :: Socket -> IO ()
listenLoop asock = do
(handle, _, _) <- accept asock
handleClient handle
listenLoop asock
main :: IO ()
main = withSocketsDo $ do
sock <- listenOn $ PortNumber 5000
listenLoop sock
To make this concurrent we use the function forkIO
which
utilizes the event-driven IO manager in GHC's runtime system to
spawn lightweight user threads which are distributed across
multiple system threads. When compiled with -threaded
the
Haskell standard library also will use non-blocking system calls
which are scheduled by the IO manager ( with epoll()
under
the hood ) and can transparently switch to threaded scheduling
for other blocking operations.
{-# LANGUAGE OverloadedStrings #-}
import Network
import Control.Monad
import Control.Concurrent
import Data.ByteString.Char8
import GHC.Conc (numCapabilities)
import System.IO (hClose, hSetBuffering, Handle, BufferMode(LineBuffering))
numCores = numCapabilities - 1
msg = "HTTP/1.0 200 OK\r\nContent-Length: 12\r\n\r\nHello World!\r\n"
handleClient :: Handle -> IO ()
handleClient handle = do
hSetBuffering handle LineBuffering
hGetLine handle
hPutStrLn handle msg
hClose handle
listenLoop :: Socket -> IO ()
listenLoop asock = do
(handle, _, _) <- accept asock
forkIO (handleClient handle)
listenLoop asock
main :: IO ()
main = withSocketsDo $ do
sock <- listenOn $ PortNumber 5000
forM_ [0..numCores] $ \n ->
forkOn n (listenLoop sock)
threadDelay maxBound
This example is admittedly very simple but does illustrate that
we can switch from serial to concurrent code in Haskell while
still preserving sequential logic. Notably this server isn't
really doing anything terribly clever to get performance, it's
simply just spawning threads and all the heavy lifting is handled
by the RTS. Yet with only a three line change the server can
utilize all available cores.
Compiling with -O2
and running with +RTS -N4 -qm -qa
I
get the following numbers on my Intel Core i5:
Requests per second: 12446.25 [#/sec] (mean)
There are other Haskell servers which do much more clever
things such as
the Warp server.
Websockets
The Warp server can utilize the async event notification system
to implement asynchronous applications using
Control.Concurrent
primitives. The prime example is so called
"realtime web programming" using websockets. In this example
we'll implement a chat room with a mutable MVar which
synchronizes messages across all threads in the server and
broadcasts messages to the clients.
$ cabal install wai-websockets
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad
import Text.Printf
import Data.Text (Text)
import Control.Concurrent
import Control.Monad.IO.Class (liftIO)
import Data.Aeson
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Network.Wai
import qualified Network.WebSockets as WS
import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai.Handler.WebSockets as WaiWS
import Network.Wai.Application.Static (defaultFileServerSettings, staticApp)
type Msg = Text
type Room = [Client]
type Client = (Text, WS.Sink WS.Hybi00)
broadcast :: Msg -> Room -> IO ()
broadcast message clients = do
T.putStrLn message
forM_ clients $ \(_, sock) -> WS.sendSink sock $ WS.textData message
app :: MVar Room -> WS.Request -> WS.WebSockets WS.Hybi00 ()
app state req = do
WS.acceptRequest req
sock <- WS.getSink
msg <- WS.receiveData
userHandler msg sock
where
userHandler msg sock = do
let client = (msg, sock)
liftIO $ T.putStrLn msg
liftIO $ modifyMVar_ state $ \s -> do
let s' = client : s
WS.sendSink sock $ WS.textData $
encode $ map fst s
return s'
userLoop state client
userLoop :: WS.Protocol p => MVar Room -> Client -> WS.WebSockets p ()
userLoop state client = forever $ do
msg <- WS.receiveData
liftIO $ readMVar state >>= broadcast (T.concat [fst client, " : ", msg])
staticContent :: Network.Wai.Application
staticContent = staticApp $ defaultFileServerSettings "."
config :: Int -> MVar Room -> Warp.Settings
config port state = Warp.defaultSettings
{ Warp.settingsPort = port
, Warp.settingsIntercept = WaiWS.intercept (app state)
}
port :: Int
port = 5000
main :: IO ()
main = do
state <- newMVar []
printf "Starting server on port %d\n" port
Warp.runSettings (config 5000 state) staticContent
In the browser we can connet to our server using Javascript:
ws = new WebSocket("ws://localhost:5000");
ws.onmessage(function (msg) {
console.log(msg);
});
ws.send("User271828");
ws.send("My message!");
Cloud Haskell
$ cabal install distributed-process distributed-process-simplelocalnet
One of the most exciting projects in Haskell is a collections of
projects developed under the Cloud
Haskell metaproject.
Cloud haskell brings language integrated messaging passing
capability to Haskell under a very simple API which provides a
foundation to build all sorts of distributed computations on top
of simple actor primitives.
The core mechanism of action is a Process
monad which
encapsulates a actor-like computation that can exchange messages
across an abstract network backend. On top of this the
distributed-process
library provides the language-integrated
ability to send arbitrary Haskell functions back and forth
between processes much like one can move code in Erlang, but
while still persiving Haskell type-safety across the message
layer. The signatures for the messaging functions are:
send :: Serializable a => ProcessId -> a -> Process ()
expect :: forall a. Serializable a => Process a
The network backend is an abstract protocol that specific
libraries ( i.e. distributed-process-simplelocalnet
) can
implement to provide the transport layer indepenent of the rest
of the stack. Many other protocols like TCP, IPC, and ZeroMQ can
be used for the network transport.
The simplest possible example is a simple ping and pong between
between several Process
. Notably we don't encode any
mechanism for binary serialization of code or data since Haskell
can derive these for us.
{-# LANGUAGE TemplateHaskell, DeriveDataTypeable, DeriveGeneric, GeneralizedNewtypeDeriving #-}
import Text.Printf
import Data.Binary
import Data.Typeable
import Control.Monad
import System.Environment (getArgs)
import Control.Concurrent (threadDelay)
import Control.Distributed.Process
import Control.Distributed.Process.Closure
import Control.Distributed.Process.Backend.SimpleLocalnet
import Control.Distributed.Process.Node (initRemoteTable,)
newtype Message = Ping ProcessId deriving (Eq, Ord, Binary, Typeable)
pingLoop :: Process ()
pingLoop = do
liftIO $ putStrLn "Connected with master node."
forever $ do
(Ping remote_pid) <- expect
say $ printf "Ping from %s" (show remote_pid)
local_pid <- getSelfPid
send remote_pid (Ping local_pid)
liftIO $ putStrLn "Pong!"
remotable [ 'pingLoop ]
master :: [NodeId] -> Process ()
master peers = do
pids <- forM peers $ \nid -> do
say $ printf "Executing remote function on %s" (show nid)
spawn nid $(mkStaticClosure 'pingLoop)
local_pid <- getSelfPid
forever $ do
forM_ pids $ \pid -> do
say $ printf "Pinging remote node %s" (show pid)
send pid (Ping local_pid)
forM_ pids $ \_ -> do
(Ping pid) <- expect
say $ printf "Received pong from %s" (show pid)
liftIO $ threadDelay 1000000
main :: IO ()
main = do
args <- getArgs
let host = "localhost"
let rtable = Main.__remoteTable initRemoteTable
case args of
["master", port] -> do
printf "Starting master on %s:%s\n" host port
ctx <- initializeBackend host port rtable
startMaster ctx master
["worker", port] -> do
printf "Starting client on %s:%s\n" host port
ctx <- initializeBackend host port rtable
startSlave ctx
otherwise -> error "Invalid arguments: master|worker <port>"
We can then spawn any number of instances from the shell:
$ runhaskell cloud.hs worker 5001
$ runhaskell cloud.hs worker 5002
$ runhaskell cloud.hs master 5003
Conclusion
Hopefully you feel for what exists in the ecosystem and feel slightly
more empowered to use the amazing tools we have.