Simple

a functional web framework in haskell

amit levy (@aalevy)

April 11, 2014 @ Heroku

$ whoami

Grad student at Stanford Work on MemCachier

A Typical Rails Learning Curve

What we really want...

What we really want...

Simple - a "framework-less" web framework

Agenda

Why write web apps in Haskell?

Throughput (req/sec) compared to Ruby, node.js

This was done informally on my laptop, but more robust benchmarks have similar results

A Brief Introduction to Haskell

Hello World

main = putStrLn "hello world"

Hello World

main = putStrLn "hello world"

Let's get a bit fancier:

main = do
  putStr "What's your name? "
  name <- getLine
  putStrLn $ "Hello " ++ name ++ "!"

A Brief Introduction to Haskell - Primitive Types

A Brief Introduction to Haskell - User-defined types

data HTMLString = UnescapedHTML String | EscapedHTML String

data Maybe a = Just a | Nothing

data Either a b = Left a | Right b

data BlogPost = BlogPost { postTitle :: String, postBody :: String }

newtype Password = Password { passwordDigest :: String }
type Point = (Double, Double)

Controller code for a Blog

main = run 3000 $ do
  settings <- newAppSettings
  controllerApp settings $ do
    get "/" $ render "welcome.html" ()

    routeName "/posts" $ do
      get "/" $ do
        posts <- findAllPosts
        render "posts/index.html" posts

      get "/:post_id" $ do
        postId <- readQueryParam' "post_id"
        routePost postId $ \post ->
            render "posts/show.html" post

      get "/:tag" $ do
        tag <- queryParam' "tag"
        posts <- findPostsByTag tag
        render "posts/index.html" posts

routePost :: DBRef Post -> (Post -> Controller BlogSettings ())
         -> Controller BlogSettings ()
routePost postId act = withConnection $ \conn -> do
  mpost <- liftIO $ findRow conn postId
  maybe (return ()) act mpost

A Web Framework in Four Lines

type ControllerState s = (Request, s)

newtype Controller s a = Controller {
  runController :: ControllerState s
                -> IO (Either Response a, ControllerState s)
}

Some building blocks to make life easier

Given a Response, construct a Controller action that responds with it:

respond :: Response -> Controller s ()
respond resp = Controller $ \s -> return (Left resp, s)

Given a value (of arbitrary type a), return it rather than responding:

return :: a -> Controller s a
return val = Controller $ \s -> return (Right val, s)

Get the Request value:

request :: Controller s Request
request = Controller $ \s -> return (Right $ fst s, s)

These are provided by Simple but don't do anything special. Just used everywhere.

Let's write a routing combinator

Remember routeName from the example?

...
routeName "posts" $ do
  ...
...

If the first thing in the Request path == the name, match this route, otherwise try the next action:

routeName :: Text -> Controller s () -> Controller s ()
routeName name (Controller next) = do
  req <- request
  if (length $ pathInfo req) > 0 && name == (head $ pathInfo req)
    then Controller $ \(req, s) -> next (popHdr req, s)
    else return ()
  where popHdr req = req { pathInfo = (tail . pathInfo $ req) }

OK, enough chatter

Let's write a blog...

Thanks!

$ cabal install simple
$ smpl create test_app
$ cd test_app
$ cabal install --only-dependencies
$ cabal run