Developing Web apps with Haskell and Yesod

July 22, 2016

These are my notes from working through Yesod Web Framework, 2nd edition.

Chapter 2


We're instructed to install Yesod and several libraries with cabal install. I prefer to do this in a sandbox, so normally I would begin with cabal sandbox init:

$ cabal sandbox init

Then proceed with the book's instructions:

$ cabal update
$ cabal install yesod yesod-bin persistent-sqlite yesod-static

Then wait roughly forever for everything to build.

Since I'm doing this from NixOS, I'll just spin up a Nix shell with all of the Yesod packages, and avoid the above cabal install entirely:

$ PACKAGES="yesod yesod-bin persistent-sqlite yesod-static"
$ nix-shell -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [$PACKAGES])"

To make this more reusable, I'll make it a function in my ~/.bashrc file:

function nix-haskell() {
  EXPR="haskellPackages.ghcWithPackages (pkgs: with pkgs; [$@])"
  nix-shell -p "$EXPR"

Now I can simply run nix-haskell <pkg1> <pkg2> ... <pkg2>:

$ nix-haskell yesod yesod-bin persistent-sqlite yesod-static

Overloaded strings

Here's a nice example of when the compiler can't figure out a type because of ambiguous type class instances:

{-# LANGUAGE OverloadedStrings
           , TypeSynonymInstances
           , FlexibleInstances

import Data.Text (Text, unpack)

class Printer a where
  println :: a -> IO ()

instance Printer String where
  println x = putStrLn x

instance Printer Text where
  println x = putStrLn $ unpack x

If we try to call println with a string-like input "Hello, world!", the compiler can't figure out whether that value has the type String or Text, so can't choose a Printer instance to use:

main :: IO ()
main = println "Hello, world!" -- fails to compile
$ curl -sL | runghc

    No instance for (Printer a0) arising from a use of ‘println’
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance Printer Text
        -- Defined at
      instance Printer String
        -- Defined at
    In the expression: println "Hello, world!"
    In an equation for ‘main’: main = println "Hello, world!"

    No instance for (Data.String.IsString a0)
      arising from the literal ‘"Hello, world!"’
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance Data.String.IsString
        -- Defined in ‘Data.ByteString.Builder’
      instance Data.String.IsString Text -- Defined in ‘Data.Text’
      instance Data.String.IsString [Char] -- Defined in ‘Data.String’
    In the first argument of ‘println’, namely ‘"Hello, world!"’
    In the expression: println "Hello, world!"
    In an equation for ‘main’: main = println "Hello, world!"

We have to tell the compiler which type we want to use:

main :: IO ()
main = println ("Hello, world!" :: String) -- compiles
$ curl -sL | runghc
Hello, world!

Chapter 3

In this chapter, we get to get our hands dirty with a bare-bones Warp/Yesod application.

Hello, world

We can start the server on localhost:3000 with curl and runghc:

$ curl -sL | runghc

And test it with curl:

$ curl localhost:3000
<!DOCTYPE html>
<html><head><title></title></head><body>Hello, world!</body></html>


Yesod uses a front controller to handle routing.

The HelloWorld example uses Yesod's declarative route specification style:

mkYesod "HelloWorld" [parseRoutes|
/ HomeR GET

In Chapter 7 we'll dig into the mkYesod TH function and the various bits it generates: a route data type, parser/render functions, a dispatch function, and some helper types.

It can be useful to compile with --ddump-splices and to generate Haddock documentation for an application to see what functions and data types are generated.

Resources and type-safe URLs

$ curl -sL | runghc

Non-HTML responses

$ curl -sL | runghc
$ curl localhost:3000
HTTP/1.1 200 OK
Date: Fri, 22 Jul 2016 17:54:50 GMT
Server: Warp/3.2.3 + Yesod/1.4.19 (core)
Content-Length: 23
Content-Type: application/json; charset=utf-8
Path=/; Expires=Fri, 22-Jul-2016 19:54:50 GMT; HttpOnly
Vary: Accept, Accept-Language

{"msg":"Hello, world!"}
$ curl -s localhost:3000 | jq .
  "msg": "Hello, world!"

Chapter 4


Hamlet syntax

$ curl -sL | runghc
<body><p>This is my page.</p>
<footer>Return to <a href="/home">Homepage</a>
$ curl -sL | runghc
<p>You are currently on page 2.
<a href="/home?page=1">Previous</a>
<a href="/home?page=3">Next</a>

Lucius syntax

$ curl -sL | runghc
.sme-class{-webkit-transition:all 4s ease;-moz-transition:all 4s ease;-ms-transition:all 4s ease;-o-transition:all 4s ease;transition:all 4s ease}