These are my notes from working through Yesod Web Framework Book, 2nd edition.
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
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
= putStrLn x
println x
instance Printer Text where
= putStrLn $ unpack x println 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 ()
= println "Hello, world!" -- fails to compile main
$ curl -s https://earldouglas.com/posts/haskell/yesod-book/chapter-2/OverloadedStringsBad.hs | runghc
/run/user/1000/runghcXXXX1804289383846930886.hs:18:8:
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
/run/user/1000/runghcXXXX1804289383846930886.hs:14:10
instance Printer String
-- Defined at
/run/user/1000/runghcXXXX1804289383846930886.hs:11:10
In the expression: println "Hello, world!"
In an equation for ‘main’: main = println "Hello, world!"
/run/user/1000/runghcXXXX1804289383846930886.hs:18:16:
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
Data.ByteString.Builder.Internal.Builder
-- 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 ()
= println ("Hello, world!" :: String) -- compiles main
$ curl -s https://earldouglas.com/posts/haskell/yesod-book/chapter-2/OverloadedStringsGood.hs | runghc
Hello, world!
In this chapter, we get to get our hands dirty with a bare-bones Warp/Yesod application.
We can start the server on localhost:3000
with
curl
and runghc
:
$ curl -s https://earldouglas.com/posts/haskell/yesod-book/chapter-3/HelloWorld.hs | runghc
And test it with curl
:
$ curl -s 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:
"HelloWorld" [parseRoutes|
mkYesod
/ HomeR GET |]
mkYesod
is a Template Haskell functionparseRoutes
is a quasiquoterHomeR
is called a resource, read as "the home
resource"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.
$ curl -s https://earldouglas.com/posts/haskell/yesod-book/chapter-3/Links.hs | runghc
$ curl -s https://earldouglas.com/posts/haskell/yesod-book/chapter-3/App.hs | runghc
$ curl -s 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
Set-Cookie:
_SESSION=UWC5A5Fnw5ruVsv8HJmjOkaaGIQS9x0Fj0xcokFkw0yCPPmLmTIg9+dU53CP+cqIVK8/WFvvPlReUIQy+UmUIy84SccmQzmScXVOYR9PXk5xoa3dQXb/i3GY6QR814ogcw3dBVOV9VY=;
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!"
}
$ curl -s https://earldouglas.com/posts/haskell/yesod-book/chapter-4/Home.hs | runghc
<body><p>This is my page.</p>
<footer>Return to <a href="/home">Homepage</a>
.</footer>
</body>
$ curl -s https://earldouglas.com/posts/haskell/yesod-book/chapter-4/SomePage.hs | runghc
<p>You are currently on page 2.
<a href="/home?page=1">Previous</a>
<a href="/home?page=3">Next</a>
</p>
$ curl -s https://earldouglas.com/posts/haskell/yesod-book/chapter-4/Lucius.hs | 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}