+ All Categories
Home > Documents > Yesod Web Framework Book

Yesod Web Framework Book

Date post: 14-Feb-2017
Category:
Upload: buixuyen
View: 226 times
Download: 0 times
Share this document with a friend
99
Yesod Web Framework Book
Transcript
Page 1: Yesod Web Framework Book

Yesod Web Framework Book

Page 2: Yesod Web Framework Book

2 | OpenTopic | TOC

Contents

Basics......................................................................................................................................5Introduction.............................................................................................................................................. 5

Type Safety.................................................................................................................................. 5Concise......................................................................................................................................... 5Performance................................................................................................................................. 5Modular........................................................................................................................................ 6A solid foundation........................................................................................................................6

Introduction to Haskell.............................................................................................................................6Language Pragmas....................................................................................................................... 6Quasi-quotation............................................................................................................................ 6Type families................................................................................................................................6

Basics....................................................................................................................................................... 6Getting Yesod...............................................................................................................................7Library versus Framework........................................................................................................... 7Hello World..................................................................................................................................7Routing......................................................................................................................................... 8Handler function...........................................................................................................................8The Foundation............................................................................................................................ 8Running........................................................................................................................................ 9Resources and type-safe URLs.................................................................................................... 9Development server....................................................................................................................10Summary.................................................................................................................................... 10

Templates............................................................................................................................................... 10Type Safety................................................................................................................................ 11Basic syntax................................................................................................................................12Tags............................................................................................................................................ 14Variables.....................................................................................................................................15Function Application..................................................................................................................15Hamlet Control Structures..........................................................................................................16Comments...................................................................................................................................17Templates in External Files........................................................................................................17Types.......................................................................................................................................... 19Lucius......................................................................................................................................... 19Hamlet Syntax............................................................................................................................ 20Summary.................................................................................................................................... 20

Widgets...................................................................................................................................................20What's in a Widget?....................................................................................................................21Building Widgets........................................................................................................................21

Yesod Typeclass.....................................................................................................................................22Rendering and Parsing URLs.....................................................................................................23defaultLayout............................................................................................................................. 25Custom error pages.....................................................................................................................27Summary.................................................................................................................................... 28

Routing and Handlers.............................................................................................................................28Route Syntax.............................................................................................................................. 28Dispatch......................................................................................................................................31The GHandler Monad.................................................................................................................32Summary.................................................................................................................................... 34

Basic Forms............................................................................................................................................35Random bananas........................................................................................................................ 35Summary.................................................................................................................................... 42

Page 3: Yesod Web Framework Book

OpenTopic | TOC | 3

Sessions.................................................................................................................................................. 42Clientsession...............................................................................................................................43Controlling sessions................................................................................................................... 43Session Operations..................................................................................................................... 44Messages.................................................................................................................................... 44Ultimate Destination.................................................................................................................. 44Summary.................................................................................................................................... 45

Persistent................................................................................................................................................ 45Solving the boundary issue........................................................................................................ 45Migrations.................................................................................................................................. 48Attributes....................................................................................................................................49Associated Types........................................................................................................................50Relations.....................................................................................................................................53Custom Fields.............................................................................................................................54Persistent: Raw SQL.................................................................................................................. 54FIXME Outline.......................................................................................................................... 55

Advanced............................................................................................................................. 56RESTful Content.................................................................................................................................... 56

Request methods........................................................................................................................ 56Representations.......................................................................................................................... 56Other request headers................................................................................................................. 59Stateless......................................................................................................................................59Summary.................................................................................................................................... 59

Authentication and Authorization.......................................................................................................... 60Scaffolding and the Site Template......................................................................................................... 60Advanced Forms.................................................................................................................................... 60Testing....................................................................................................................................................60Sending Email........................................................................................................................................ 60Deploying your Webapp........................................................................................................................ 60

Warp........................................................................................................................................... 61FastCGI...................................................................................................................................... 62Desktop.......................................................................................................................................63CGI on Apache...........................................................................................................................63FastCGI on lighttpd....................................................................................................................64CGI on lighttpd...........................................................................................................................64

Internationalization.................................................................................................................................64Creating a Subsite...................................................................................................................................64

Hello World................................................................................................................................65Web Client Code.................................................................................................................................... 66

JSON Web Service.....................................................................................................................66Low Level Tricks................................................................................................................................... 68

Appendices...........................................................................................................................69Enumerator Package...............................................................................................................................69

Iteratees...................................................................................................................................... 69Enumerators............................................................................................................................... 73Enumeratees............................................................................................................................... 77

Web Application Interface..................................................................................................................... 80The Interface.............................................................................................................................. 81Hello World................................................................................................................................82Middleware.................................................................................................................................82

Blog posts that should be chapters......................................................................................................... 83Frequently Asked Questions.................................................................................................................. 83

Why does the scaffolded site use cassiusFileDebug and juliusFileDebug, but does not use hamletFileDebug?83Migration Guide: 0.6 to 0.7....................................................................................................................84

No Warnings.............................................................................................................................. 84Install yesod................................................................................................................................84

Page 4: Yesod Web Framework Book

4 | OpenTopic | TOC

Update cabal file.........................................................................................................................84Update your Hamlet templates...................................................................................................84cabal install.................................................................................................................................84Show, Read and Eq for Html..................................................................................................... 85MonadInvertIO to MonadPeelIO............................................................................................... 85urlRenderOverride......................................................................................................................85runDB: need liftIOHandler.........................................................................................................85mime-mail Part constructor........................................................................................................85Feed, formerly known as AtomFeed.......................................................................................... 86fileLookupDir no more...............................................................................................................86MultiParamTypeClasses.............................................................................................................86String to ByteString....................................................................................................................86devel-server.hs............................................................................................................................86lift, not liftHandler......................................................................................................................86

Migration Guide: 0.7 to 0.8....................................................................................................................86No Warnings.............................................................................................................................. 87Install yesod................................................................................................................................87Update cabal file.........................................................................................................................87cabal install.................................................................................................................................87persistent-template..................................................................................................................... 87Language Extensions..................................................................................................................87External File............................................................................................................................... 87toHtml.........................................................................................................................................88MonadPeelIO to MonadControlIO.............................................................................................88String to Text..............................................................................................................................88Persist Keys................................................................................................................................ 88Explicit Type Signatures............................................................................................................ 88

Examples..............................................................................................................................89Example: Blog........................................................................................................................................89Example: Ajax........................................................................................................................................91Example: Form.......................................................................................................................................93Example: Widgets.................................................................................................................................. 95Example: Generalized Hamlet............................................................................................................... 96Example: Pretty YAML......................................................................................................................... 97Example: Internationalization................................................................................................................ 98

Page 5: Yesod Web Framework Book

OpenTopic | Basics | 5

Basics

IntroductionSince web programming began, people have been trying to make the development process a more pleasant one. As acommunity, we have continually pushed new techniques to try and solve some of the lingering difficulties of securitythreats, the stateless nature of HTTP, the multiple languages (HTML, CSS, Javascript) necessary to create a powerfulweb application, and more.

Yesod attempts to ease the web development process by playing to the strengths of the Haskell programminglanguage. Haskell's strong compile-time guarantees of correctness not only encompass types; referential transparencyensures that we don't have any unintended side effects. Pattern matching on algebraic data types can help guaranteewe've accounted for every possible case. By building upon Haskell, entire classes of bugs disappear.

Unfortunately, using Haskell isn't enough. The web, by its very nature, is not type safe. Even the simplest case ofdistinguishing between an integer and string is impossible: all data on the web is transferred as raw bytes, evadingour best efforts at type safety. Every app writer is left with the task of validating all input. I call this problem theboundary issue: as much as your application is type safe on the inside, every boundary with the outside world stillneeds to be sanitized.

Type Safety

This is where Yesod comes in. By using high-level declarative techniques, you can specify the exact input types youare expecting. And the process works the other way as well: using a process of type-safe URLs, you can make surethat the data you send out is also guaranteed to be well formed.

The boundary issue is not just a problem when dealing with the client: the same problem exists when persisting andloading data. Once again, Yesod saves you on the boundary by performing the marshaling of data for you. You canspecify your entities in a high-level definition and remain blissfully ignorant of the details.

Concise

We all know that there is a lot of boilerplate coding involved in web applications. Wherever possible, Yesod tries touse Haskell's features to save your fingers the work:

• The forms library reduces the amount of code used for common cases by leveraging the Applicative type class.• Routes are declared in a very terse format, without sacrificing type safety.• Serializing your data to and from a database is handled automatically via code generation.

In Yesod, we have two kinds of code generation. To get your project started, we provide a scaffolding tool to set upyour file and folder structure. However, most code generation is done at compile time via meta programming. Thismeans your generated code will never get stale, as a simple library upgrade will bring all your generated code up-to-date.

But for those who like to stay in control, and know exactly what their code is doing, you can always run closer to thecompiler and write all your code yourself.

Performance

Haskell's main compiler, the GHC, has amazing performance characteristics, and is improving all the time. Thischoice of language by itself gives Yesod a large performance advantage over other offerings. But that's not enough:we need an architecture designed for performance.

Our approach to templates is one example: by allowing HTML, CSS and JavaScript to be analyzed at compile time,Yesod both avoids costly disk I/O at runtime and can optimize the rendering of this code. But the architecturaldecisions go deeper: we use advanced techniques such as enumerators and builders in the underlying libraries to make

Page 6: Yesod Web Framework Book

6 | OpenTopic | Basics

sure our code runs in constant memory, without exhausting precious file handles and other resources. By offeringhigh-level abstractions, you can get highly compressed and properly cached CSS and JavaScript.

Yesod's flagship web server, Warp, is the fastest Haskell web server around. When these two pieces of technology arecombined, it produces one of the fastest web application deployment solutions available.

Modular

Yesod has spawned the creation of dozens of packages, most of which are usable in a context outside of Yesod itself.One of the goals of the project is to contribute back to the community as much as possible; as such, even if you arenot planning on using Yesod in your next project, a large portion of this book may still be relevant for your needs.

Of course, these libraries have all been designed to integrate well together. Using the Yesod Framework should giveyou a strong feeling of consistency throughout the various APIs.

A solid foundation

I remember once seeing a PHP framework advertising support for UTF-8. In the Haskell world, we usually have theopposite problem: there are a number of packages providing powerful and well-designed support for the problem.The Haskell community is constantly pushing the boundaries finding the cleanest, most efficient solutions for eachchallenge.

The downside of such a powerful ecosystem is the complexity of choice. By using Yesod, you will already have mostof the tools chosen for you, and you can be guaranteed they work together. Of course, you always have the option ofpulling in your own solution.

As a real-life example, Yesod and Hamlet (the default templating language) use blaze-builder for textualcontent generation. This choice was made because blaze provides the fastest interface for generating UTF-8 data.Anyone who wants to use one of the other great libraries out there, such as text, should have no problem dropping itin.

Introduction to HaskellHaskell is a powerful, fast, type-safe, functional programming language. This book takes as an assumption that youare already familiar with most of the basics of Haskell. There are two wonderful books for learning Haskell, both ofwhich are available for reading online:

• Learn You a Haskell for Great Good!• Real World Haskell

Yesod relies on a few features in Haskell that most introductory tutorials do not cover. Though you will rarely need tounderstand how these work, It's always best to start off with a good appreciation for what your tools are doing. Thesechapter will cover those features.

Language Pragmas

FIXME

Quasi-quotation

FIXME

Type families

FIXME

Basics

Page 7: Yesod Web Framework Book

OpenTopic | Basics | 7

Getting Yesod

The rest of this book will assume you have Yesod installed. You'll need to:

• Install the Haskell Platform• Run cabal update• Run cabal install alex happy• Run cabal install yesod• In general, you should follow the instruction at Yesod in 5 minutes.

Note: It is generally recommended to create new projectsusing the Yesod scaffolder tool by running yesod init.This book will mostly do things manually for instructivepurposes, but for real world projects, even I use thescaffolder to get the basic setup correct.

Library versus Framework

I'm going to be a bit bold and say the defining line between a library and a framework is that a framework tells youhow to lay out your code into a file/folder structure. You may not agree with this definition, but it's useful to explainhow this book will begin.

The Yesod Web Framework comes with a tool that automatically generates a full site template with a bunch of bellsand whistles. This is the recommended way to get started on a new Yesod application. This added convenience,however, hides away some of the important details going on behind the scenes.

So to start off, we're going to be treating Yesod as a library. Having to explicitly write all the code is a good exerciseto get started. Later on, we'll introduce the scaffolding tool and describe the standard layout of a Yesod project.

Hello World

Let's get this book started properly: a simple web page that says Hello World:

-- START{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesod

data HelloWorld = HelloWorld

mkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]

instance Yesod HelloWorld where approot _ = ""

getHomeR = defaultLayout [$hamlet|Hello World!|]

main = warpDebug 3000 HelloWorld-- STOP

advanced:

I have purposely left out the type signature of getHomeRin this snippet because it looks scarier than it really is, andbecause in real life code with the scaffolding tool it wouldlook different anyway. For the curious, the type signaturewould be:

getHomeR :: GHandler HelloWorld HelloWorld RepHtml

Page 8: Yesod Web Framework Book

8 | OpenTopic | Basics

-- But the scaffolding tool defines an alias:type Handler = GHandler HelloWorld HelloWorld

-- So the type signature would just begetHomeR :: Handler RepHtml

If you save that code in helloworld.hs and run it with runhaskell helloworld.hs, you'll get a webserver running on port 3000. If you point your browser to http://localhost:3000, you'll get the following HTML:

<!DOCTYPE html><html><head><title></title></head><body>Hello World!</body></html>

Routing

Like most modern web frameworks, Yesod follows a front controller pattern. This means that every request to aYesod application enters at the same point and is routed from there. As a contrast, in systems like PHP and ASP youoften times create a number of different files, and the web server automatically directs requests to the relevant file.

Lines 6 through 8 set up this routing system. We see our only resource defined on line 7. We'll give full details ofthe syntax later, but this line creates a resource named HomeR, which accepts GET requests at the root (/) of ourapplication.

Yesod sees this resource declaration, and determines to call the getHomeR handler function whenever it receives arequest for HomeR. The function name follows the simple pattern of request method, in lowercase, followed by theresource name.

Handler function

Most of the code you write in Yesod lives in handler functions. This is where you process user input, performdatabase queries and create responses. In our simple example, we create a response using the defaultLayout function.By default, this is simply an HTML wrapper that creates a doctype, html, head and body tags. As we'll see later, thisfunction can be overridden to do much more.

That funny [$hamlet|Hello World!|] is a quasi-quotation. It allows us to embed arbitrary text in our Haskellcode, process it with a specific function and have that generate Haskell code, all at compile time. In our case, we feedthe string "Hello World!" to the hamlet quasi-quoter.

advanced:

While quasi quotation is great for small code snippets, andwonderful for making single-file examples, it does notscale well to production levels, since it mixes logic andpresentation in the same file. When we discuss templates,you will see how to put your templates in external files.

Hamlet is the default HTML templating engine in Yesod. Together with its siblings Cassius and Julius, you can createHTML, CSS and Javascript in a fully type-safe and compile-time-checked manner. We'll see much more about thiswhen we discuss widgets.

The Foundation

The word "HelloWorld" shows up on lines 4, 6, 10 and 15, yet the datatype doesn't seem to actually do anythingimportant. In fact, this seemingly irrelevant piece of code is central to how Yesod works. Each Yesod application hasa single datatype, referred to as its foundation.

Line 4 of our example defines this simple datatype. Line 6 does something a bit more interesting: it associates therouting rule we define on line 7 with this datatype. Each foundation must be an instance of the Yesod typeclass; we dothis on line 10. We'll get into much more detail on the Yesod typeclass and the approot method in the Yesod typeclasschapter.

Page 9: Yesod Web Framework Book

OpenTopic | Basics | 9

advanced:

By the way, the word Yesod (####) means foundation inHebrew.

Running

Once again we mention HelloWorld in our main function. Our foundation contains all the information we need toroute and respond to requests in our application, now we just need to convert it into something that can run. A greatfunction for this in Yesod is warpDebug, which runs the Warp webserver with debug output enabled on the specifiedport (here, it's 3000).

advanced:

In addition to warpDebug, Yesod provides the warpfunction for a no-debug-output server (useful forproduction), as well as develServer, which doesautomatic code reloading. The scaffolded site sets up yourdevelopment and production builds automatically.

One of the great features of Yesod is that you aren't tied down to a single deployment strategy. Yesod is built on topof the Web Application Interface (WAI), allowing it to run on FastCGI, SCGI, Warp, or even as a desktop applicationusing the Webkit library. We'll discuss some of these options in the deployment chapter. And at the end of thischapter, we will explain the development server.

Resources and type-safe URLs

In our hello world, we defined just a single resource (HomeR). A web application is usually much more exciting withmore than one page on it. Let's take a look:

-- START{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesod

data Links = Links

mkYesod "Links" [$parseRoutes|/ HomeR GET/page1 Page1R GET/page2 Page2R GET|]

instance Yesod Links where approot _ = ""

getHomeR = defaultLayout [$hamlet|<a href="@{Page1R}">Go to page 1!|]getPage1R = defaultLayout [$hamlet|<a href="@{Page2R}">Go to page 2!|]getPage2R = defaultLayout [$hamlet|<a href="@{HomeR}">Go home!|]

main = warpDebug 3000 Links-- STOP

Overall, this is the same. Our foundation is now Links instead of HelloWorld, and in addition to the HomeR resource,we've added Page1R and Page2R. As such, we've also added two more handler functions: getPage1R and getPage2R.

The only truly new feature is inside the hamlet quasi-quotation on lines 15-17. We'll delve into syntax later, but wecan see that:

<a href="@{Page1R}">Go to page 1!

creates a link to the Page1R resource. The important thing to note here is that Page1R is a data constructor. Bymaking each resource a data constructor, we have a feature called type-safe URLs. Instead of splicing together

Page 10: Yesod Web Framework Book

10 | OpenTopic | Basics

strings to create URLs, we simply create a plain old Haskell value. By using at-sign interpolation (@{...}), Yesodautomatically renders those values to textual URLs before sending things off to the user.

Development server

One of the advantages interpreted languages have over compiled languages is fast prototyping: you save changes toa file and hit refresh. If we want to make any changes to our Yesod apps above, we'll need to call runhaskell fromscratch, which can be a bit tedious.

Fortunately, there's a nice solution to this: wai-handler-devel embeds a Haskell interpreter and automaticallyreloads code changes for you. This can be a great way to develop your Yesod projects, and when you're ready tomove to production, you can compile against a more efficient backend. The Yesod site template comes built in with ascript to do this for you. This gives you the best of both worlds: rapid prototyping and fast production code.

It's a little bit more involved to set up your code to be used by wai-handler-devel, so our examples will just usewarpDebug. But as a simple example, try saving the following as HelloWorld.hs:

-- START{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}module HelloWorld where

import Yesod

data HelloWorld = HelloWorld

mkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]

instance Yesod HelloWorld where approot _ = ""

getHomeR = defaultLayout [$hamlet|Hello World!|]

withHelloWorld f = toWaiApp HelloWorld >>= f

And then run your code with:

wai-handler-devel 3000 HelloWorld withHelloWorld

This will run a development server on port 3000, using module HelloWorld and the function withHelloWorld. Trymaking changes to the HelloWorld.hs file and reloading: they should show up automatically. In order to shut downthe server, type "q" and hit enter on the command line.

Summary

Every Yesod application is built around a foundation datatype. We associate some resources with said datatype anddefine some handler functions, and Yesod handles all of the routing. These resources are also data constructors, whichlets us have type-safe URLs.

By being built on top of WAI, Yesod applications can run with a number of different backends. warpDebug is an easyway to get started, as it's included with Yesod. For rapid development, wai-handler-devel is a good choice. And whenyou're ready to move to production, there are high-performance options like Warp and FastCGI.

TemplatesYesod is built upon the hamlet package, which provides three templating systems: Hamlet (HTML), Cassius (CSS)and Julius (Javascript). These systems are all available for use outside the realm of Yesod.

Page 11: Yesod Web Framework Book

OpenTopic | Basics | 11

advanced:

Starting with Yesod 0.8, there is a new CSS templatelanguage, Lucius. While Cassius uses white space to implynesting, Lucius is a superset of CSS itself, and thereforeuses braces/semicolons. Both languages are currentlyfully supported, first-class Yesod citizens. If we end upwith a clear winner between them in the future, we mightconsider deprecating one.

In this chapter, we will explore the syntax of these languages, how they can be combined to form completedocuments, and where to actually put the content.

To start off, our examples will use the same quasi-quotation syntax as the basics chapter, and we will move on fromthere.

advanced:

Just a little heads-up: in order to simplify the examples inthis chapter, we're going to assume the OverloadedStringslanguage extension. If you rather not use this extension,you'll need to make some minor modifications, such asreplacing:

setTitle "Home Page"

with

setTitle $ string "Home Page"

The goal of hamlet syntax is essentially to remove all redundancy from html. By staying closer to html, we can:

• avoid learning arbitrarily new syntax• appeal to a wider variety of programmers and• appeal to designers

Just by making white space significant and removing closing tags, we eliminate the main source of invalid html.With that, and some id/class shortcuts, and making attribute quoting optional, we have eliminated the main sources oftedium in html.

Type Safety

One of the biggest features of Yesod is its pervasive type safety. This was the original impetus for the creationof Hamlet. As such, all variable interpolations gets checked at compile time. Hamlet supports three forms ofinterpolation:

• Any instance of the ToHtml typeclass. This includes String and Html. This is also where Hamlet shows great XSS(XSS)-attack resilience: whenever interpolating a String, Hamlet automatically escapes all HTML entities. This iscalled variable interpolation.

• Type-safe URLs. We mentioned in the basics chapter that each URL can be represented as a Haskell value, andthen we use a render function to convert those values into strings. Hamlet allows you to interpolate those type-safeURLs directly. This is called URL interpolation.

• Other Hamlet templates. This is great for creating a chunk of code that will be reused in other templates. This iscalled embedding.

Cassius provides support for the first two forms of interpolation, but instead of the ToHtml typeclass, there is theToCss typeclass. Embedding does not make as much sense with CSS. Julius provides support for all three.

advanced:

Cassius used to provide support for something similarto embedding, called mixins. This was removed due toimplementation details regarding the whitespace syntax.We now also have Lucius (described below) that providesthese features.

Page 12: Yesod Web Framework Book

12 | OpenTopic | Basics

In order to perform an interpolation, you enter the interpolation character, followed by the variable inside braces. Forexample, My name is #{name}. The hash is used for variable interpolation, at-sign (@) for URL interpolation,and caret (^) for embedding.

Basic syntax

We have already seen some Hamlet in the basics chapter. Let's have a quick review of our Hello World example:

-- START{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesod

data HelloWorld = HelloWorld

mkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]

instance Yesod HelloWorld where approot _ = ""

getHomeR = defaultLayout [$hamlet|Hello World!|]

main = warpDebug 3000 HelloWorld-- STOP

Line 13 shows our Hamlet quasi-quotation, surrounded by [$hamlet| and |]. The Haskell compiler knows toparse the internal code, convert it to Haskell, and then compile that.

But that's a really boring HTML page: it's just the text "Hello World!" without any tags! Let's see something a littlebit more interesting:

{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesoddata HelloWorld = HelloWorldmkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]instance Yesod HelloWorld where approot _ = ""-- STARTgetHomeR = defaultLayout [$hamlet|<h1>Hello World!<p>Here are some of my favorite links:<ul> <li> <a href=http://www.yesodweb.com/>Yesod Web Framework Docs <li> <a href=http://www.haskell.org/>Haskell Homepage<p>Thanks for visiting!|]-- STOPmain = warpDebug 3000 HelloWorld

Overall, Hamlet syntax looks fairly similar to HTML. However, there is one important difference: instead of closingtags manually, nesting is determined by indentation level. The goal of Hamlet is to provide a less error-prone, moreDRY (DRY) syntax for HTML, without introducing a completely foreign language that developers and designers willneed to relearn.

Similarly, Cassius is also whitespace-sensitive:

{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesoddata HelloWorld = HelloWorld

Page 13: Yesod Web Framework Book

OpenTopic | Basics | 13

mkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]instance Yesod HelloWorld where approot _ = ""getHomeR = defaultLayout $ do [$hamlet|<h1>Hello World!<p>Here are some of my favorite links:<ul> <li> <a href=http://docs.yesodweb.com/>Yesod Web Framework Docs <li> <a href=http://www.haskell.org/>Haskell Homepage<p>Thanks for visiting!|] addCassius-- START [$cassius|h1 color: greenul > li:first-child border-left: 5px solid orange|]-- STOPmain = warpDebug 3000 HelloWorld

On the other hand, Julius is mostly a pass-through format, allowing you to write your Javascript however you like:

{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesoddata HelloWorld = HelloWorldmkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]instance Yesod HelloWorld where approot _ = ""getHomeR = defaultLayout $ do addScriptRemote "https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js" [$hamlet|<h1>Hello World!<p>Here are some of my favorite links:<ul> <li> <a href=http://docs.yesodweb.com/>Yesod Web Framework Docs <li> <a href=http://www.haskell.org/>Haskell Homepage<p>Thanks for visiting!|] addCassius [$cassius|h1 color: greenul > li:first-child border-left: 5px solid orange|] addJulius-- START [$julius|$(function(){ $("h1").after("<p><a href='#' id='mylink'>Never click me</a></p>"); $("#mylink").click(function(){ alert("You clicked me!!! How dare you!"); return false; });});|]-- STOP

Page 14: Yesod Web Framework Book

14 | OpenTopic | Basics

main = warpDebug 3000 HelloWorld

Tags

Besides the whitespace rules, Hamlet also provides a few more features to make your life a little bit easier.

{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesoddata HelloWorld = HelloWorldmkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]instance Yesod HelloWorld where approot _ = ""

getHomeR = defaultLayout $ do-- START [$hamlet|<p #my-id .my-class This paragraph has an ID and a class. It also has # <b>bold \ and # <i>italic , and shows you how to control whitespace.|]-- STOPmain = warpDebug 3000 HelloWorld

Let's start on line 2. In Hamlet, instead of writing id="my-id", you can use the css-selector: #my-id. The sameapplies to classes: .my-class.

Next, compare line 2 with lines 4 and 6: line 2 does not complete the tag, it just leaves it open (no greater than sign),while lines 4 and 6 do complete it. In Hamlet, the greater than sign is optional. However, it is required if you want toput content on the same line as the tag.

The last thing to note is how we handle whitespace. On line 3, we want to force an extra space at the end of the line,so that there will be a space between the word has and the <b> tag. When we place the hash at the end of the line byitself, Hamlet ignores it entirely, and therefore only the space is kept. (If you really want to output a hash at the end ofa line, simply put two, and Hamlet will ignore the second one.)

advanced:

Technically speaking, the trailing hash isn't necessary:if you leave a space at the end of a line, Hamlet willnotice it and use it. However, some text editors willsilently trim trailing whitespace, and it can be confusing toreaders of your code. Therefore, using the trailing hash isrecommended.

The other trick is whitespace at the beginning of a line. Since Hamlet is nested using whitespace, adding extraspaces will only increase the nesting level. To fix this, on line 5, we start the line with a backslash. Once we have thebackslash, the nesting level is determined, and any whitespace we see is interpreted literally. (Like the trailing hash, ifyou need to start a line with a backslash, just use two of them.)

advanced:

When it comes to tag attributes, you can write them eitherwith or without quotes. In other words, the following linesare equivalent:

<a target="_blank" href="@{MyDest}"<a target=_blank href=@{MyDest}

Either way, Hamlet will generate HTML which quotes theattribute values. The only time you must use the quotes inHamlet is when your attribute values contain whitespace.

Page 15: Yesod Web Framework Book

OpenTopic | Basics | 15

Variables

There's nothing really special about the above examples: they just show some alternate syntax for HTML and CSS.The nice thing is to see how the templates are able to interact with a surrounding program. (For the moment, acceptthe addHamlet, addCassius and addJulius functions as magic; we will cover them when we discuss widgets.)

{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesoddata HelloWorld = HelloWorldmkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]instance Yesod HelloWorld where approot _ = ""

getHomeR = defaultLayout $ do-- START let name = "Michael" :: String let nameId = "name" :: String

addHamlet [$hamlet|<h1>Hello World!<p> Welcome to my system. Your name is # <span ##{nameId}>#{name} . Enjoy your stay!<p <a href=@{HomeR}>Return Home|]

addCassius [$cassius|##{nameId} color: green|]

addJulius [$julius|alert("Welcome #{name}");|]-- STOPmain = warpDebug 3000 HelloWorld

We see two variable interpolations on line 8. The first one looks a little bit funny, but the first hash is indicating thatthis is an ID. In other words, it is equivalent to <span id=#{nameId}>. The second interpolation simply outputsthe name into the content of the page.

On line 11, we use a type-safe URL: Yesod will automatically render HomeR into a proper URL to that resource. Online 15, we reference the same nameId variable in our Cassius as we did earlier in Hamlet. This exposes a nice trick:instead of typing in identifiers in HTML and CSS directly, we can use Haskell variables. This gives two benefits:

• If you make a typo, the compiler will catch it.• Yesod can automatically generate unique identifiers (using the newIdent function) for you, and you can then

use those to synchronize your Hamlet, Cassius and Julius code. This produces much more composable code, sinceyou are guaranteed that names will not clash.

Finally, line 20 simply let's you know that Julius can do this too.

Function Application

You can do much more than just reference variables with interpolation: you can do complete function applications.Some examples are in order:

{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}-- STARTimport Yesod

Page 16: Yesod Web Framework Book

16 | OpenTopic | Basics

data HelloWorld = HelloWorldtype Author = Stringtype Title = String

mkYesod "HelloWorld" [$parseRoutes|/ HomeR GET/blog/#Author/#Title BlogR GET|]

instance Yesod HelloWorld where approot _ = ""

getHomeR = defaultLayout $ do let myPhrase = "ring ring ring ring ring ring ring, BANANA PHONE" :: String let myNumber = 12345 addHamlet [$hamlet|<h1>Welcome to the blog homepage.<p We highly recommend that you read a blog post on # <a href=@{BlogR "einstein" "relativity"}>general relativity .<p Also, the last 8 letters of the phrase # <i>#{myPhrase} \ are #{reverse $ take 8 $ reverse myPhrase}.<p And the first two digits of # <i>#{show myNumber} \ are #{take 2 (show myNumber)}.|]-- STOPgetBlogR _ _ = return ()main = warpDebug 3000 HelloWorld

The key lines are 22, 27 and 31. You can see that:

• You can include string and numeric literals in interpolations.• You can apply functions and constructors to values.• Binding order works the same as in Haskell.• You can use the dollar sign ($) operator like you can in Haskell to control binding order.• You can also use parentheses.

The syntax for the contents of an interpolation are identical amongst Hamlet, Cassius and Julius, and do not dependon the type of interpolation (variable, URL or embedding).

Hamlet Control Structures

In addition to simple interpolations, Hamlet (though not Cassius or Julius) provides a few control structures, namely:

• if/elseif/else• forall• maybe/nothing

For all control structures, the line must begin with a dollar sign and be followed immediately by the name of thecontrol structure. Let's see a concrete example:

{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesoddata HelloWorld = HelloWorldmkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]instance Yesod HelloWorld where approot _ = ""

Page 17: Yesod Web Framework Book

OpenTopic | Basics | 17

getHomeR = defaultLayout $ do-- START let people = ["Michael", "Miriam", "Eliezer", "Gavriella"] :: [String] let isFather x = x == "Michael" isMother x = x == "Miriam" let getAge "Michael" = Just 26 getAge "Eliezer" = Just 3 getAge _ = Nothing [$hamlet|<h1>People<ul $forall person <- people <li> <b>#{person} \ #

$if isFather person This is the father of the family. $elseif isMother person This is the mother of the family. $else This is a child.

\ #

$maybe age <- getAge person This person is #{show age} years old. $nothing I do not know how old this person is.|]-- STOPmain = warpDebug 3000 HelloWorld

Line 10 shows the syntax of a forall. In particular, we mimick the variable binding syntax of do-notation to grab eachvariable from a list. Also, the pattern of li inside of $forall inside of ul is a very common one in Hamlet.

Lines 15 through 20 show usage of if/elseif/else. There is no variable binding here, and usage is fairly straight-forward. Notice that as long as we are inside the $forall block, we can refer to the person variable.

Finally, 24 and 26 show us maybe and nothing. Like forall, maybe uses do-notation variable binding for its value. Onthe other hand, nothing does not have any values associated, and therefore no binding is performed.

Comments

FIXME

Templates in External Files

Quasi-quoting your templates can be convenient, as no extra files are needed, your template is close to your code,and recompilation happens automatically whenever your template changes. On the other hand, this also clutters yourHaskell code with templates and requires a recompile for any change in the template. Hamlet provides two sets offunctions for including an external template:

• hamletFile/cassiusFile/juliusFile• hamletFileDebug/cassiusFileDebug/juliusFileDebug

What's very nice about these is that they have the exact same type signature, so they can be exchanged withoutchanging your code otherwise. These functions are not exported by the Yesod module and must be imported directlyfrom their respective modules (Text.Hamlet, Text.Cassius, Text.Julius). You'll see in a second why that is.

Usage is very straight-forward. Assuming there is a Hamlet template stored in "my-template.hamlet", you couldwrite:

defaultLayout $(hamletFile "my-template.hamlet")

Page 18: Yesod Web Framework Book

18 | OpenTopic | Basics

For those not familiar, the dollar sign and parantheses indicate a Template Haskell interpolation. Using the second setof functions, the above would become:

defaultLayout $(hamletFileDebug "my-template.hamlet")

Note that in order to use this, you need to enable the TemplateHaskell language extension. You can do so by addingthe following line to the top of your source file:

{-# LANGUAGE TemplateHaskell #-}

So why do we have two sets of functions? The first fully embeds the contents of the template in the code at compiletime and never looks at the template again until a recompile. This is ideal for a production environment: compile yourcode and you have no runtime dependency on any template files. It also avoids a runtime penalty of needing to read afile.

The debug set of functions is intended for development. These functions work a little bit of magic: at compile time,they inspect your template, determine which variables they reference, and generate some Haskell code to loadup those variables. At run time, they read in the template again and feed in those variables. This has a number ofimplications:• Changes to your template become immediately visible upon saving the file, no recompile required.• If you introduce new variables to the template that were not there before, you'll need to recompile. This might

require you manually nudging GHC to recompile the Haskell file, since it won't think anything has changed.• Due to some of the tricks needed to pull this off, some of the more corner cases of templates are not supported.

For example, using a forall to bind a function to a variable. This is an obscure enough case that it shouldn't be anissue.

This is also the reason why Yesod does not export these functions by default. The Yesod scaffolding tool creates aSettings.hs file which exports these functions, in a slightly modified form, and chooses whether to use the debug orregular version based upon build flags. Long story short: it automatically uses the debug version during developmentand non-debug version during production.

Excepting very short templates, this is probably how you'll write most of your templates in Yesod. The typical filestructure is to create hamlet, cassius and julius folders and place the respective templates in each. Each template has afilename extension matching the template language. In other words, you'd typically have:

# hamlet/homepage.hamlet<h1>Hello World!

# cassius/homepage.cassiush1 color: green

# julius/homepage.juliusalert("Don't you hate it when you get an alert when you open a page?"); # Settings.hs, paraphrasingimport qualified Text.Hamletimport qualified Text.Cassiusimport qualified Text.Julius

hamletFile x = Text.Hamlet.hamletFileDebug $ "hamlet/" ++ x ++ ".hamlet"- - same for cassius and julius- - when moving to production, you would just remove Debug

# And finally your handler codeimport SettingsgetHomeR = defaultLayout $ do setTitle "Homepage" addHamlet $(hamletFile "homepage") addCassius $(cassiusFile "homepage") addJulius $(juliusFile "homepage")

For simplicity, most of the examples in this book will use quasi-quoted syntax. Just remember that you can alwaysswap this out for external files.

Page 19: Yesod Web Framework Book

OpenTopic | Basics | 19

Types

I have purposely skirted the issue of what the value of these templates is. Let's start off with Cassius: Text.Cassiusdefines a datatype called Css. Then the value of a cassius template is:

type Cassius url = (url -> [(String, String)] -> String) -> Css

That's a little bit intimidating, so let's break it down. Cassius takes a type parameter, url, which is the datatype of ourtype-safe URL. A Cassius value itself is a function: the argument to the function is a URL rendering function, whichgiven a type-safe URL value and a list of query string parameters, produces a URL. Using that, a Cassius value canproduce a Css value. Yesod itself knows how to apply the URL rendering function and unwrap the Css value, sounless you want to dig under the surface, you won't need to get your hands dirty.

Julius is almost identical. Instead of Css, it defines a type Javascript, and then has a datatype:

type Julius url = (url -> [(String, String)] -> String) -> Javascript

Now, you're probably expecting me to say that the same holds true for Hamlet. Well... it sort of does. Instead ofdefining its own Html datatype, Hamlet borrows the datatype from blaze-html. But then it does define a Hamlettype synonym:

type Hamlet url = (url -> [(String, String)] -> String) -> Html

However, Hamlet has an extra feature that Cassius and Julius don't have: polymorphism. This means that a Hamlettemplate can take on various values, including Html and Hamlet. Yesod defines an instance for Widget, meaning thata Hamlet template can be used directly as a widget. In fact, we have been abusing that fact every time we have writtendefaultLayout [$hamlet|...|] without using addHamlet.

When we get to widgets, we will explore why this polymorphism is so useful. As a heads up: it really saves the daywhen dealing with forms.

Lucius

Cassius is a great pair to Hamlet: both indicate nesting via indentation and do away with various "line noise"characters. However, there are a few practical issues with Cassius:• You cannot copy-paste a CSS file into a Cassius template.• It's difficult to add extra features like block nesting (to be described below).Whether or not this is a serious problem is a subjective matter. To provide Yesod users with the greatest flexibility,there is now a second CSS language: Lucius. Cassius and Lucius share a number of things: variable/URLinterpolation syntax is identical, and they end up producing the same datatypes. They are therefore completelyinterchangeable, and one can even use them simultaneously in a single project.

The difference is that Lucius is designed as a superset of CSS. It should be possible to take any valid CSS file, copy itinto a Lucius file, and get the same results (albeit a bit minified).

advanced:

Those familiar with it might note a corrolary to the Sass/Scss divide in the Ruby world.

Besides being a CSS pass-through with variable interpolation, Lucius also intends to add convenience features. Fornow, this is limited to block nesting. This means that Lucius will convert:

foo, bar { baz, bin { color: red; }}

into

foo baz, foo bin, bar baz, bar bin { color: red;}

Page 20: Yesod Web Framework Book

20 | OpenTopic | Basics

Hamlet Syntax

The examples above have not covered every aspect of Hamlet. This section gives a complete overview of the Hamletsyntax for learning and reference purposes.

Interpolation Hamlet supports four forms of interpolation:#{var} interpolates a normal variable, whichmust be an instance of ToHtml. @{url}interpolates a type-safe URL. @?{urlParams}interpolates a URL/get parameters pair.^{template} interpolates another template.(Note: in Hamlet 0.9, we will introduce a fifthform of interpolation to handle internationalizedmessages.)

Tags Any line that begins with a less-than sign begins atag. The name of the tag must immediately follow.(If no name is given, div is assumed.) There arethen a number of whitespace-separated attributes,either given as key=value, #id or .class.

Conditional attributes Both key=value and classattributes can be prefixed with acondition. So for example: <inputtype=checkbox :isSelected:selected=true>or <a :isCurrent:.currenthref=@{MyRouteR}>.

Sealing the tag Sealing the tag is optional. However, if you do sealit, you can put some raw content after the tag.

Close tag Close tags are not used at all in Hamlet.

Indentation Nesting of tags and content is implied viaindentation level. For these purposes, a hard tabhas the value of four spaces.

Conditionals You can do conditionals via $if/$elseif/$else.

Loops $forall can be applied to any instance ofFoldable.

with binding $with can be used to bind a new variable.

Summary

Yesod has templating languages for HTML, CSS and Javascript. All of them allow variable interpolation, safehandling of URLs and embedding sub-templates. Since the code is dealt with at compile time, you can use thecompiler as your friend and get strong type safety guarantees. Oh, and XSS vulnerabilities get handled automatically.

There are three ways to embed the templates: through quasi-quotation, regular external and debug external. Quasi-quotation is great for small, simple templates that won't be changing often. Debug mode is great for development,and since it has the same type signature as the regular external functions, you can easily switch to using them for yourproduction code.

WidgetsOne of the challenges in web development is that we have to coordinate three different client-side technologies:HTML, CSS and Javascript. Worse still, we have to place these components in different locations on the page: CSS

Page 21: Yesod Web Framework Book

OpenTopic | Basics | 21

in a style tag in the head, Javascript in a script tag in the head, and HTML in the body. And never mind if you want toput your CSS and Javascript in separate files!

In practice, this works out fairly nicely when building a single page, because we can separate our structure (HTML),styling (CSS) and logic (Javascript). But when we want to build modular pieces of code that can be easily composed,it can be a headache to coordinate all three pieces separately. Widgets are Yesod's solution to the problem. They alsohelp with the issue of including libraries, such as jQuery, one time only.

Our three template languages- Hamlet, Cassius and Julius- provide the raw tools for constructing your output.Widgets provide the glue that allows them to work together seamlessly.

What's in a Widget?

At a very superficial level, an HTML document is just a bunch of nested tags. This is the approach most HTMLgeneration tools take: you simply define hierarchies of tags and are done with it. But let's imagine that I want to writea component of a page for displaying the navbar. I want this to be "plug and play": I simply call the function at theright time, and the navbar is inserted at the correct point in the hierarchy.

This is where our superficial HTML generation breaks down. Our navbar likely consists of some CSS and JavaScriptin addition to HTML. By the time we call the navbar function, we have already rendered the <head> tag, so it istoo late to add a new <style> tag for our CSS declarations. Under normal strategies, we would need to break up ournavbar function into three parts: HTML, CSS and JavaScript, and make sure that we always call all three pieces.

Widgets take a different approach. Instead of viewing an HTML document as a monolithic tree of tags, widgets see anumber of distinct components in the page. In particular:• The title• External stylesheets• External Javascript• CSS declarations• Javascript code• Arbitrary <head> content• Arbitrary <body> contentDifferent components have different semantics. For example, there can only be one title, but there can be multipleexternal scripts and stylesheets. However, those external scripts and stylesheets should only be included once.Arbitrary head and body content, on the other hand, has no limitation (someone may want to have five lorem ipsumblocks after all).

The job of a widget is to hold onto these disparate components and apply proper logic for combining different widgetstogether. This consists of things like taking the first title set and ignoring others, applying nub to the list of externalscripts and stylesheets, and simply concatenating head and body content.

advanced:

In general, you should avoid nub since it has verybad performance. Usually when you are looking foruniqueness, you do not care about order, and thereforemap head . group . sort is more efficient than acall to nub. However, in our case, order is important: wewould not want to include jQuery UI before we includejQuery. Therefore, we are stuck with nub.

Building Widgets

In the templates chapter, we already began an initial look at how to construct widgets. Let's begin more formally here.Widgets have a monad instance, so we can use do notation for building up larger widgets from smaller parts. For eachof the components of a widget listed above, there is a corresponding primitive.

Let's see how we can combine two simple primitives: setTitle and addHtml:

{-# LANGUAGE TypeFamilies, QuasiQuotes, OverloadedStrings #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}import Yesod

Page 22: Yesod Web Framework Book

22 | OpenTopic | Basics

data HelloWorld = HelloWorldmkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]instance Yesod HelloWorld where approot _ = ""-- STARTgetHomeR = defaultLayout $ do setTitle "Hello World" addHtml [$hamlet|Hello World!|]-- STOPmain = warpDebug 3000 HelloWorld

The one thing to note is that setTitle takes a value of type Html as an argument, not a String. As usual, therecommendation is to run your code with OverloadedStrings turned on.

FIXME The rest of this chapter is under construction.

Polymorphic Hamlet

We have already mentioned that a Hamlet template is polymorphic. This is best seen in the context of widgets.

{-# LANGUAGE TypeFamilies, QuasiQuotes, OverloadedStrings #-}{-# LANGUAGE MultiParamTypeClasses #-}{-# LANGUAGE TemplateHaskell #-}import Yesoddata HelloWorld = HelloWorldmkYesod "HelloWorld" [$parseRoutes|/ HomeR GET|]instance Yesod HelloWorld where approot _ = ""-- STARTgetHomeR = defaultLayout $ do setTitle "Polymorphic Hamlet" addHtml [$hamlet|<p>I was added with addHtml|] addHamlet [$hamlet|<p>I was added with addHamlet|] addWidget [$hamlet|<p>I was added with addWidget|]-- STOPmain = warpDebug 3000 HelloWorld

As you can see, all three functions addHtml, addHamlet and addWidget can work on a Hamlet template. Thequestion arises: why do we need three? One reason is that while a Hamlet template itself is polymorphic, not allvalues are. For example, you may have a user-supplied Html value that you wish to add to your page: addHamlet andaddWidget will not work; you will have to use addHtml.

However, there is a more subtle point as well. While in theory a Hamlet template is polymorphic, this actuallydepends upon what values are embedded inside it (using ^{...} interpolation). For example, [$hamlet|^{someValue}|] will have the same type as someValue; if someValue is of type Hamlet, then the template willbe as well.

Yesod TypeclassEvery one of our Yesod applications requires an instance of the Yesod typeclass. So far, we've only seen the approotmethod, with the cryptic definition approot _ = "", and defaultLayout. In this chapter, we'll explore themeaning of the approot method, along with many other methods in the Yesod typeclass.

The Yesod typeclass gives us a central place for defining settings for our application. Excluding the approot method,everything else has a default definition which is usually the right thing. But in order to build a powerful, customizedapplication, you'll usually end up overriding at least a few of these methods.

Page 23: Yesod Web Framework Book

OpenTopic | Basics | 23

Rendering and Parsing URLs

We've already mentioned how Yesod is able to automatically render type-safe URLs into a textual URL that can beinserted into an HTML page. Let's say we have a route definition that looks like:

mkYesod "MyApp" [$parseRoutes|/some/path SomePathR GET]

If we place SomePathR into a hamlet template, how does Yesod render it? Yesod always tries to construct absoluteURLs. This is especially useful once we start creating XML sitemaps and Atom feeds, or sending emails. But in orderto construct an absolute URL, we need to know the domain name of the application.

You might think to just get that information from the user's request, but we still need to deal with ports. And evenif we get the port number from the request, are we using HTTP or HTTPS? And even if you know that, such anapproach would break one of our RESTful principles: depending on how the user submitted a request would generatedifferent URLs. For example, we would generate different URLs depending if the user connected to "example.com"or "www.example.com".

And finally, Yesod doesn't make any assumption about where you host your application. For example, I may have amostly static site (http://static.example.com/), but I'd like to stick a Yesod-powered Wiki at /wiki/. There is no reliableway for an application to determine what subpath it is being hosted from. So instead of doing all of this guesswork,Yesod needs you to tell it the application root.

So using the wiki example, you would write your Yesod instance as:

instance Yesod MyWiki where approot _ = "http://static.example.com/wiki"

Notice that there is no trailing slash there. Next, when Yesod wants to construct a URL for SomePathR, itdetermines that the relative path for SomePathR is "/some/path", appends that to your approot and creates "http://static.example.com/wiki/some/path".

This also explains our cryptic approot _ = "": for our examples in the book, we're always serving from the rootof the domain (in our case, localhost). By using an empty string, SomePathR renders to "/some/path", which worksjust fine. In real life applications, however, you should use a real application root.

And by the way, the site template generated by the scaffolding tool automatically uses conditional compilation toswitch between development and production builds, so you can easily test on one domain- like localhost- and servefrom a different domain.

advanced:

You might be wondering: why does approot take that firstargument if it is always ignored? There are two reasons:

• It is needed by Haskell's type system to determinewhich instance of Yesod to use for grabbing thetypeclass.

• And actually, the first argument is not always ignored.For example, if you want to load the application rootvalue from a configuration file, the most logical placeto store that value is in the foundation datatype, andthen for the approot function to grab the value fromthere.

joinPath

In order to convert a type-safe URL into a text value, Yesod uses two helper functions. The first is therenderRoute method of the RenderRoute typeclass. Every type-safe URL is an instance of this typeclass.renderRoute simply converts a value into a list of path pieces. For example, our SomePathR from above wouldbe converted into ["some", "path"].

Page 24: Yesod Web Framework Book

24 | OpenTopic | Basics

advanced:

Actually, renderRoute produces both the path pieces anda list of query-string parameters. The default instances ofrenderRoute always provide an empty list of query stringparameters. However, it is possible to override this. Onenotable case is the static subsite, which puts a hash of thefile contents in the query string for caching purposes.

The other function is the joinPath method of the Yesod typeclass. This function takes the four arguments: thefoundation value, the application root, a list of path segments and a list of query string parameters, and returns atextual URL. The default implementation does the "right thing": it separates the path pieces by forward slashes,prepends the application root and appends the query string.

If you are happy with default URL rendering, you should not need to modify it. However, if you want to modify URLrendering to do things like append a trailing slash, this would be the place to do it.

cleanPath

The flip side to joinPath is cleanPath. Let's look at how it gets used in the dispatch process:

1. The path info requested by the user is split into a series of path pieces.2. If any prefix of the path pieces matches a subsite, then dispatching is passed off to the subsite.3. Otherwise, we pass the path pieces to the cleanPath function.4. If cleanPath indicates a redirect (a Left response), then a 301 response is sent to the client. This is used to force

canonical URLs (eg, remove extra slashes).5. Otherwise, we try to dispatch to our non-subsite routes using the response from cleanPath (a Right). If this works,

we return a response. Otherwise, we return a 404.

This combination allows subsites to retain full control of how their URLs appear, yet allows master sites to havemodified URLs. As a simple example, let's see how we could modify Yesod to always produce trailing slashes onURLs:

-- START{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import Yesodimport Web.Routes (encodePathInfo)import Blaze.ByteString.Builder.Char.Utf8 (fromText)import qualified Data.Text as Timport Control.Arrow ((***))

data Slash = Slash

mkYesod "Slash" [$parseRoutes|/ RootR GET/foo FooR GET|]

instance Yesod Slash where approot _ = ""

joinPath _ ar pieces' qs' = fromText $ ar `T.append` T.pack ('/' : encodePathInfo pieces qs) where qs = map (T.unpack *** T.unpack) qs' pieces = map T.unpack $ if null pieces' then [] else pieces' ++ [""]

-- We want to keep canonical URLs. Therefore, if the URL is missing a -- trailing slash, redirect. But the empty set of pieces always stays the -- same. cleanPath _ [] = Right [] cleanPath _ s

Page 25: Yesod Web Framework Book

OpenTopic | Basics | 25

| dropWhile (not . T.null) s == [""] = -- the only empty string is the last one Right $ init s -- Since joinPath will append the missing trailing slash, we simply -- remove empty pieces. | otherwise = Left $ filter (not . T.null) s

getRootR = defaultLayout [$hamlet|<p <a href=@{RootR}>RootR<p <a href=@{FooR}>FooR|]

getFooR = getRootR

main = warpDebug 3000 Slash

First, let's look at our joinPath implementation. This is copied almost verbatim from the default Yesodimplementation, with one difference: when the initial pieces is not empty, we append an extra empty string to theend. When dealing with path pieces, an empty string will simply append another slash. So adding an extra emptystring will force a trailing slash. The only time to not want this trailing slash is when we are looking at the root of theapplication, because adding an extra slash will result in two slashes in a row.

cleanPath is a little bit trickier. First, we check for the empty path like before, and if so pass it through as-is. We useRight to indicate that a redirect is not necessary. The next clause is actually checking for two different possible URLissues:• There is a double slash, which would show up as an empty string in the middle of our paths.• There is a missing trailing slash, which would show up as the last piece not being an empty string.Assuming neither of those conditions hold, then only the last piece is empty, and we should dispatch based on all butthe last piece. However, if this is not the case, we want to redirect to a canonical URL. In this case, we strip out allempty pieces and do not bother appending a trailing slash, since joinPath will do that for us.

defaultLayout

Most websites like to apply some general template to all of their pages. defaultLayout is the recommendedapproach for this. While you could just as easily define your own function and call that instead, when you overridedefaultLayout all of the Yesod-generated pages (error pages, authentication pages) automatically get this style.

Overriding is very straight-forward: we use widgetToPageContent to convert a Widget to a title, head tags andbody tags, and then use hamletToRepHtml to convert a Hamlet template into a RepHtml. We can even use widgetfunctions like addCassius from within defaultLayout. A simple example should make this all clear:

{-# LANGUAGE TypeFamilies, QuasiQuotes #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}{-# LANGUAGE OverloadedStrings #-}import Yesoddata Layout = LayoutmkYesod "Layout" [$parseRoutes|/ RootR GET|]instance Yesod Layout where approot _ = ""-- START defaultLayout contents = do PageContent title headTags bodyTags <- widgetToPageContent $ do addCassius [$cassius|#body font-family: sans-serif#wrapper width: 760px margin: 0 auto|] addWidget contents hamletToRepHtml [$hamlet|

Page 26: Yesod Web Framework Book

26 | OpenTopic | Basics

!!!

<html> <head> <title>#{title} ^{headTags} <body> <div id="wrapper"> ^{bodyTags}|]-- STOPgetRootR = defaultLayout $ do setTitle $ string "Root test" addCassius [$cassius|body color: red|] addHamlet [$hamlet|<h1>Hello|]main = warpDebug 4000 Layout

advanced:

The three exclamation points (!!!) on a line by themselvesis used to insert a doctype statement in Hamlet. By default,this will be the HTML 5 doctype line, ie <!DOCTYPEhtml>. You should make certain to only insert this onceper output. In most use cases, the only place you will needit is in your defaultLayout function.

getMessage

Even though we haven't covered sessions yet, I'd like to mention getMessage here. A common pattern in webdevelopment is needed to set a message in one handler and display it in another. For example, if a user POSTs a form,you may want to redirect him/her to another page along with a "Form submission complete" message.

To facilitate this, Yesod comes built in with a pair of functions: setMessage sets a message in the user session, andgetMessage retrieves the message (and clears it, so it doesn't appear a second time). It's recommended that you put theresult of getMessage into your defaultLayout. For example:

{-# LANGUAGE TypeFamilies, QuasiQuotes #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}{-# LANGUAGE OverloadedStrings #-}import Yesoddata Layout = LayoutmkYesod "Layout" [$parseRoutes|/ RootR GET/msg MsgR GET|]instance Yesod Layout where approot _ = ""-- START defaultLayout contents = do PageContent title headTags bodyTags <- widgetToPageContent contents mmsg <- getMessage hamletToRepHtml [$hamlet|!!!

<html> <head> <title>#{title} ^{headTags} <body> $maybe msg <- mmsg <div #message>#{msg}

Page 27: Yesod Web Framework Book

OpenTopic | Basics | 27

^{bodyTags}|]-- STOPgetRootR = defaultLayout [$hamlet|<a href="@{MsgR}">message|]getMsgR = setMessage (string "foo") >> redirect RedirectTemporary RootR >> return ()main = warpDebug 4000 Layout

We'll cover getMessage/setMessage in more detail when we discuss sessions.

Custom error pages

One of the marks of a professional web site is a properly designed error page. Yesod gets you a long way there byautomatically using your defaultLayout for displaying error pages. But sometimes, you'll want to go even further. Forthis, you'll want to override the errorHandler method:

{-# LANGUAGE TypeFamilies, QuasiQuotes, OverloadedStrings #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}import Yesoddata Layout = LayoutmkYesod "Layout" [$parseRoutes|/ RootR GET|]instance Yesod Layout where approot _ = ""-- START errorHandler NotFound = fmap chooseRep $ defaultLayout $ do setTitle "Request page not located" addWidget [$hamlet|<h1>Not Found<p>We appologize for the inconvenience, but the requested page could not be located.|] errorHandler other = defaultErrorHandler other-- STOPgetRootR = defaultLayout [$hamlet|\Hello World|]main = warpDebug 4000 Layout

Here we specify a custom 404 error page. We can also use the defaultErrorHandler when we don't want to write acustom handler for each error type. Due to type constraints, we need to start off our methods with "fmap chooseRep",but otherwise you can write a typical handler function.

In fact, you could even use special responses like redirects:

{-# LANGUAGE TypeFamilies, QuasiQuotes, OverloadedStrings #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}import Yesoddata Layout = LayoutmkYesod "Layout" [$parseRoutes|/ RootR GET|]instance Yesod Layout where approot _ = ""-- START errorHandler NotFound = redirect RedirectTemporary RootR errorHandler other = defaultErrorHandler other-- STOPgetRootR = defaultLayout [$hamlet|\Hello World|]main = warpDebug 4000 Layout

Note: Even though you can do this, I don't actuallyrecommend such practices. A 404 should be a 404.

Page 28: Yesod Web Framework Book

28 | OpenTopic | Basics

Summary

The Yesod typeclass has a number of overrideable methods that allow you to configure your application. Besidesapproot, they are all optional. By using built-in Yesod constructs like defaultLayout and getMessage, you'll get aconsistent look-and-feel throughout your site, including pages automatically generated by Yesod such as error pagesand authentication.

We haven't covered all the methods in the Yesod typeclass in this chapter. Some of them relate to topics we have notyet covered (such as authentication), and will be discussed in those chapters. For a full listing of methods available,you should always consult the Haddock documentation.

Routing and HandlersIf we look at Yesod as a Model-View-Controller framework, routing and handlers make up the controller. Forcontrast, let's describe two other routing approaches used in other web development environments:

• Dispatch based on file name. This is how PHP and ASP work, for example.• Have a centralized routing function that parses routes based on regular expressions. Django and Rails follow this

approach.

Yesod is closer in principle to the latter technique. Even so, there are significant differences. Instead of using regularexpressions, Yesod matches on pieces of a route. Instead of having a one-way route-to-handler mapping, Yesod hasan intermediate data type (called the route datatype, or a type-safe URL) and creates two-way conversion functions.

Coding this more advanced system manually is tedious and error prone. Therefore, Yesod relies heavily on TemplateHaskell and Quasi-Quotation to automatically generate this code for you. This chapter will explain the syntax ofthe routing declarations, give you a glimpse of what code is generated for you, and explain the interaction betweenrouting and handler functions.

Route Syntax

Pieces

The first thing Yesod does when it gets a request (well, maybe not the first) is split up the requested path into pieces.The pieces are simply tokenized at all forward slashes. So:

toPieces "/" = []toPieces "/foo/bar/baz/" = ["foo", "bar", "baz", ""]

You may notice that there are some funny things going on with trailing slashes, or double slashes ("/foo//bar//"), or afew other things. Yesod believes in having canonical URLs; if someone requests a URL with a trailing slash, or witha double slash, they automatically get a redirect to the canonical version. This follows the RESTful principle of oneURL for one resource, and can help with your search rankings.

What this means for you is that you needn't concern yourself with the exact structure of your URLs: you can safelythink about pieces of a path, and Yesod automatically handles intercalating the slashes and escaping problematiccharacters.

If, by the way, you want more fine-tuned control of how paths are split into pieces and joined together again, you'llwant to look at the cleanPath and joinPath methods in the Yesod typeclass chapter.

Types of Pieces

When you are declaring your routes, you have three types of pieces at your disposal:

Static This is a plain string that must be matched againstprecisely in the URL.

Dynamic single This is a single piece (ie, between two forwardslashes), but can be a user-submitted value. This isthe primary method of receiving extra user input

Page 29: Yesod Web Framework Book

OpenTopic | Basics | 29

on a page request. These pieces begin with a hash(#) and are followed by a data type. The datatypemust be an instance of SinglePiece.

Dynamic multi The same as before, but can receive multiplepieces of the URL. This must always be the lastpiece in a resource pattern. It is specified by anasterisk (*) followed by a datatype, which mustbe an instance of MultiPiece. Multi piecesare not as common as the other two, though theyare very important for implementing features likestatic trees representing file structure or wikis witharbitrary hierarchies.

Let us take a look at some standard kinds of resource patterns you may want to write. Starting simply, the root of anapplication will just be /. Similarly, you may want to place your FAQ at /page/faq.

Now let's say you are going to write a Fibonacci website. You may construct your URLs like /fib/#Int. Butthere's a slight problem with this: we do not want to allow negative numbers or zero to be passed into our application.Fortunately, the type system can protect us:

{-# LANGUAGE TypeFamilies, QuasiQuotes, GeneralizedNewtypeDeriving #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}{-# LANGUAGE OverloadedStrings #-}import Yesodimport qualified Data.Text as Timport Web.Routes.Quasidata Fibs = Fibs-- STARTnewtype Natural = Natural Int -- we might even like to go with Word here-- STOP deriving (Show, Read, Eq, Num, Ord)-- STARTinstance SinglePiece Natural where toSinglePiece (Natural i) = T.pack $ show i fromSinglePiece s = case reads $ T.unpack s of (i, _):_ | i < 1 -> Nothing | otherwise -> Just $ Natural i [] -> Nothing-- STOPmkYesod "Fibs" [$parseRoutes|/fibs/#Natural FibsR GET|]instance Yesod Fibs where approot _ = ""fibs = 1 : 1 : zipWith (+) fibs (tail fibs)getFibsR :: Natural -> GHandler Fibs Fibs RepPlaingetFibsR (Natural i) = return $ RepPlain $ toContent $ show $ fibs !! (i - 1)main = warpDebug 3000 Fibs

On line 1 we define a simple newtype wrapper around Int to protect ourselves from invalid input. We can seethat SinglePiece is a typeclass with two methods. toSinglePiece does nothing more than convert to a Text.fromSinglePiece attempts to convert a Text to our datatype, returning Nothing when this conversion isimpossible. By using this datatype, we can ensure that our handler function is only ever given natural numbers,allowing us to once again use the type system to battle the boundary issue.

Defining a MultiPiece is just as simple. Let's say we want to have a Wiki with at least two levels of hierarchy; wemight define a datatype such as:

{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell #-}{-# LANGUAGE OverloadedStrings #-}import Yesod

Page 30: Yesod Web Framework Book

30 | OpenTopic | Basics

import Data.Text (Text)import Web.Routes.Quasidata Fibs = Fibs-- STARTdata Page = Page Text Text [Text] -- 2 or moreinstance MultiPiece Page where toMultiPiece (Page x y z) = x : y : z fromMultiPiece (x:y:z) = Just $ Page x y z fromMultiPiece _ = Nothing-- STOPmain = return ()

Resource name

Each resource pattern also has a name associated with it. That name will become the constructor for the type safe URLdatatype associated with your application. Therefore, it has to start with a capital letter. By convention, these resourcenames all end with a capital R. There is nothing forcing you to do this, it is just common practice.

The exact definition of our constructor depends upon the resource pattern it is attached to. Whatever datatypesare included in single and multi pieces of the pattern become arguments to the datatype. This gives us a 1-to-1correspondence between our type safe URL values and valid URLs in our application.

advanced:

This doesn't necessarily mean that every value is aworking page, just that it is is a potentially valid URL.As an example, that value PersonR "Michael" maynot resolve to a valid page if there is no Michael in thedatabase.

Let's get some real examples going here. If you had the resource patterns /person/#String named PersonR,/year/#Int named YearR and /page/faq named FaqR, you would end up with a route data type roughlylooking like:

data MyRoute = PersonR String | YearR Int | FaqR

If a user requests the relative URL of /year/2009, Yesod will convert it into the value YearR 2009. /person/Michael becomes PersonR "Michael" and /page/faq becomes FaqR. On the other hand, /year/two-thousand-nine, /person/michael/snoyman and /page/FAQ would all result in 404 errors without everseeing your code.

advanced:

Throughout the above discussion, I used the terms type-safe URLs and routes datatype interchangeably. Don't beconfused, they are the exact same thing. They just soundbetter in different contexts.

Handler specification

The last piece of the puzzle when declaring your resources is how they will be handled. There are three options inYesod:

• You have a single handler function which should be used for all request methods.• You want to write a separate handler function for each request method you will support. All other request method

will generate a 405 Bad Method response.• You want to pass off to a subsite.

The first two are very easily specified. A single handler function will be a line with just a resource pattern and theresource name, such as /page/faq FaqR. In this case, the handler function must be named handleFaqR.

Page 31: Yesod Web Framework Book

OpenTopic | Basics | 31

A separate handler for each request method will be the same, plus a list of request methods. The request methodsmust be ALL CAPITAL LETTERS. For example, /person/#String PersonR GET POST DELETE. In thiscase, you would need to define the three handler functions getPersonR, postPersonR and deletePersonR.

Subsites are a very useful— but complicated— topic in Yesod. We will cover writing subsites later, but using them isnot too difficult. The most commonly used subsite is the static subsite, which serves static files for your application.In order to serve static files from /static, you would need a resource line like:

/static StaticR Static getStatic

In this line, /static just says where in your URL structure to serve the static files from. There is nothing magicalabout the word static, you could easily replace it with /my/non-dynamic/files.

The next word, StaticR, gives the resource name. The next two words are what specify that we are using a subsite.Static is the name of the subsite foundation datatype, and getStatic is a function that gets a Static valuefrom a value of your main application's foundation datatype.

Let's not get too caught up in the details of subsites now. We will look more closely at the static subsite in thescaffolded site chapter.

Dispatch

Once you have specified your routes, Yesod will take care of all the pesky details of dispatch for you. You justneed to make sure to provide the appropriate handler functions. For subsite routes, you do not need to write anyhandler functions, but you do for the other two. We mentioned the naming rules above (MyHandlerR GET ->getMyHandlerR, MyOtherHandlerR -> handleMyOtherHandlerR). Now we need the type signature.

Return Type

Let's look at a simple handler function:

{-# LANGUAGE TypeFamilies, QuasiQuotes #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}{-# LANGUAGE OverloadedStrings #-}import Yesoddata Simple = Simple-- STARTmkYesod "Simple" [$parseRoutes|/ HomeR GET|]

getHomeR :: GHandler subsite Simple RepHtmlgetHomeR = defaultLayout [$hamlet|<h1>This is simple|]-- STOPinstance Yesod Simple where approot _ = ""main = warpDebug 3000 Simple

Look at the type signature of getHomeR. Unfortunately, there is a lot of complexity here to dig through. We willcover the GHandler monad in more detail later. Next come subsite and Simple. This is where we need to startheading down the rabbit hole...

The same way your application has a foundation datatype and associated type-safe URL datatype, so does everysubsite. As an example, the Static subsite's foundation datatype has information on how to look up a requested file.The Authentication subsite has a URL datatype that provides login, logout and various other actions.

The GHandler monad needs information on both the current subsite and current master site. Most of the time, theseare the same! When you are writing your typical handler functions, you only have a single foundation going on. Soin the code snippet above, the type signature for getHomeR could also have been GHandler Simple SimpleRepHtml without any problems.

For now, just accept it as a strange quirk that you need to deal with this extra type parameter. In fact, it isrecommended to use a type synonym like type Handler = GHandler MyApp MyApp at the beginning of

Page 32: Yesod Web Framework Book

32 | OpenTopic | Basics

your code (the scaffolded site does this for you). And when we get to the subsite chapter we will explore why thisawkwardness is a necessity, and the huge benefits we reap as a result.

ChooseRep

That just leaves us one thing: RepHtml. When we discuss representations we will explore the why of things more;for now, we are just interested in the how.

As you might guess, RepHtml is a datatype for HTML responses. But as you also may guess, web sites need to returnresponses besides HTML. CSS, Javascript, images, XML are all necessities of a website. Therefore, the return valueof a handler function can be any instance of HasReps.

HasReps is a powerful concept that allows Yesod to automatically choose the correct representation of your databased on the client request. For now, we will focus just on simple instances such as RepHtml, which only provide onerepresentation.

Arguments

But not every route is as simple as the HomeR we just defined. Take for instance our PersonR route from earlier. Thename of the person needs to be passed to the handler function. This translation is very straight-forward, and hopefullyintuitive. For example:

{-# LANGUAGE TypeFamilies, QuasiQuotes #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}{-# LANGUAGE OverloadedStrings #-}import Yesodimport Data.Text (Text)import qualified Data.Text as Tdata Args = Args-- STARTtype Handler = GHandler Args Args

type Texts = [Text]mkYesod "Args" [$parseRoutes|/person/#Text PersonR GET/year/#Integer/month/#Text/day/#Int DateR/wiki/*Texts WikiR GET|]

getPersonR :: Text -> Handler RepHtmlgetPersonR name = defaultLayout [$hamlet|<h1>Hello #{name}!|]

handleDateR :: Integer -> Text -> Int -> Handler RepPlain -- text/plainhandleDateR year month day = return $ RepPlain $ toContent $ T.concat [month, " ", T.pack $ show day, ", ", T.pack $ show year]

getWikiR :: [Text] -> Handler RepPlaingetWikiR = return . RepPlain . toContent . T.unwords-- STOPinstance Yesod Args where approot _ = ""main = warpDebug 3000 Args

The arguments have the types of the dynamic pieces for each route, in the order specified. Also, notice how we areable to use both RepHtml and RepPlain.

The GHandler Monad

The vast majority of code you write in Yesod sits in the GHandler monad. If you are approaching this from an MVC(MVC) background, your GHandler code is the Controller. Some important points to know about GHandler:

• It is an instance of MonadIO, so you can run any IO action in your handlers with liftIO. By the way, liftIOis exported by the Yesod module for your convenience.

• GHandler is really a monad transformer stack providing a number of different components.

Page 33: Yesod Web Framework Book

OpenTopic | Basics | 33

• A Reader component provides access to immutable information about a request and the environment: thefoundation value, request headers and much more.

• A Writer component allows you to add extra response headers.• A State component deals with session variables, allowing them to be both read, written and deleted. Sessions are

discussed in their own chapter.• An Error component deals with short-circuiting. Despite the name, these are not necessarily errors: you can use

this for sending static files and redirecting in addition to sending error responses.advanced:

And on top of all this, there is in fact a largergeneralization from GHandler: a GGHandler. While aGHandler wraps around an Iteratee, which allows it toread the request body, a GGHandler can wrap around anymonad, including directly around IO. This generalizationis necessary for dealing with catching exceptions. We willdiscuss this in more depth later.

The remainder of this chapter will give a brief introduction to some of the most common functions living in theGHandler monad. I am specifically not covering any of the session functions; that will be addressed in the sessionschapter.

Application Information

There are a number of functions that return information about your application as a whole, and give no informationabout individual requests. Some of these are:

getYesod Returns your applicaton foundation value. If youstore configuration values in your foundation, youwill probably end up using this function a lot.

getYesodSub Get the subsite foundation value. Unless you areworking in a subsite, this will return the samevalue as getYesod.

getUrlRender Returns the URL rendering function, whichconverts a type-safe URL into a String. Mostof the time- like with Hamlet- Yesod calls thisfunction for you, but you may occassionally needto call it directly.

getUrlRenderParams A variant of getUrlRender that convertsboth a type-safe URL and a list of query-stringparameters. This function handles all percent-encoding necessary.

Request Information

The most common information you will want to get about the current request is the requested path, the query stringparameters and POSTed form data. The first of those is dealt with in the routing, as described above. The other twoare best dealt with using the forms module.

That said, you will sometimes need to get the data in a more raw format. For this purpose, Yesod exposes theRequest datatype along with the getRequest function to retrieve it. This gives you access to the full list of GETparameters, cookies, and preferred languages. There are some convenient functions to make these lookups easier,such as lookupGetParam, lookupCookie and languages. For raw access to the POST parameters, youshould use runRequest.

If you need even more raw data, like request headers, you can use waiRequest to access the WAI request value.

Short Circuiting

The following functions immediately end execution of a handler function and return a result to the user.

Page 34: Yesod Web Framework Book

34 | OpenTopic | Basics

redirect Sends a redirect response to the user. You canspecify whether you want a 301, 302 or 303 statuscode. This function takes a type-safe URL as adestination. There are also redirectStringand redirectParams variants.

notFound Return a 404 response. This can be useful if a userrequests a database value that doesn't exist.

permissionDenied Return a 403 response with a specific errormessage.

invalidArgs A 400 response with a list of invalid arguments.

sendFile Sends a file from the filesystem with a specifiedcontent type. This is the preferred way to sendstatic files, since the underlying WAI handler maybe able to optimize this to a sendfile systemcall. Using readFile for sending static filesshould not be necessary.

sendResponse Send a normal HasReps response with a 200 statuscode. This is really just a convenience for whenyou need to break out of some deeply nested codewith an immediate response.

Response Headers

setCookie Set a cookie on the client. Instead of taking anexpiration date, this function takes a cookieduration in minutes. Remember, you won't see thiscookie using lookupCookie until the followingrequest.

deleteCookie Tells the client to remove a cookie. Once again,lookupCookie will not reflect this change untilthe next request.

setHeader Set an arbitrary response header.

setLanguage Set the preferred user language, which will showup in the result of the languages function.

cacheSeconds Set a Cache-Control header to indicate how manyseconds this response can be cached. This can beparticularly useful if you are using varnish on yourserver.

neverExpires Set the Expires header to the year 2037. You canuse this with content which should never expire,such as when the request path has a hash valueassociated with it.

alreadyExpired Sets the Expires header to the past.

expiresAt Sets the Expires header to the specified date/time.

Summary

Routing and dispatch is arguably the core of Yesod: it is from here that our type-safe URLs are defined, and themajority of our code is written within the GHandler monad. This chapter covered some of the most important andcentral concepts of Yesod, so it is important that you properly digest it.

Page 35: Yesod Web Framework Book

OpenTopic | Basics | 35

This chapter also hinted at a number of more complex Yesod topics that we will be covering later. But you should beable to write some very sophisticated web applications with just the knowledge you have learned up until here.

Basic FormsI've mentioned the boundary issue already: whenever data enters or leaves an application, we need to validate ourdata. Probably the most difficult place this occurs is forms. Coding forms is complex; in an ideal world, we'd like asolution that addresses the following problems:

• Ensure data is valid.• Marshal string data in the form submission to Haskell datatypes.• Generate HTML code for displaying the form.• Generate Javascript to do clientside validation and provide more user-friendly widgets, such as date pickers.• Build up more complex forms by combining together simpler forms.

The form system used in Yesod is built around many of the concepts in formlets; if you are familiar with that work,it will give you a head start here. In this chapter, we'll start with some real-life examples of using forms, and then getinto the low-level details of how they work so you can abuse their full power.

advanced:

The examples below all assume that you have theOverloadedStrings extension enabled. To do so, simplyput the following line at the beginning of your code:

{-# LANGUAGE OverloadedStrings #-}

Random bananas

Let's start off with a silly example: we want to create a website that generates a random number of some object. Itneeds to know the minimum number of objects, the maximum number of objects, the name of a single object and thename of a plural object (eg, book and books, mouse and mice).

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, OverloadedStrings #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}import Yesodimport System.Randomimport Control.Applicativeimport Data.Text (Text)import qualified Data.Text as T

data Rand = Randtype Handler = GHandler Rand Rand

mkYesod "Rand" [$parseRoutes|/ RootR GET|]

instance Yesod Rand where approot _ = ""

data Params = Params { minNumber :: Int , maxNumber :: Int , singleWord :: Text , pluralWord :: Text }

paramsFormlet :: Maybe Params -> Form s m Params-- Same as: paramsFormlet :: Formlet s m ParamsparamsFormlet mparams = fieldsToTable $ Params <$> intField "Minimum number" (fmap minNumber mparams)

Page 36: Yesod Web Framework Book

36 | OpenTopic | Basics

<*> intField "Maximum number" (fmap maxNumber mparams) <*> stringField "Single word" (fmap singleWord mparams) <*> stringField "Plural word" (fmap pluralWord mparams)

getRootR :: Handler RepHtmlgetRootR = do (res, form, enctype) <- runFormGet $ paramsFormlet Nothing output <- case res of FormMissing -> return "Please fill out the form to get a result." FormFailure _ -> return "Please correct the errors below." FormSuccess (Params min max single plural) -> do number <- liftIO $ randomRIO (min, max) let word = if number == 1 then single else plural return $ T.concat ["You got ", T.pack $ show number, " ", word] defaultLayout [$hamlet|<p>#{output}<form enctype="#{enctype}"> <table> ^{form} <tr> <td colspan="2"> <input type="submit" value="Randomize!">|]-- STOP

main = warpDebug 3001 Rand

In lines 19-24, we set up a new datatype to hold all of the parameter information we need for this program. There'snothing special at all about this datatype, it's a POHD (Plain Old Haskell Datatype). Lines 26-32 are where weintroduce the forms API in Yesod.

First, notice the type signature: this function takes a Maybe Params. This allows us to either start with a blankform (Nothing) or initialize our form to some default value. This can be especially useful when creating an add/editinterface. The return type of the function is a Form; we'll deal more with that later. Also, since this pattern of taking aMaybe value is so common, it has a synonym defined as Formlet (line 27).

Let's skip down to line 29: we start off with <$>: this stresses the fact that we deal with forms using theirApplicative instance. This allows us to check all fields in a form for validation errors. Lines 30-32 all start with<*>; you can see more information on the Applicative typeclass on the wikibook Applicative page.

Continuing along line 29, we call intField. This is doing exactly what you expect: specifying that we want anintegral value. "Minimum number" is the label displayed to the user, and the last part of the line gives the initial valueof the field.

So how exactly is this initial value used? Well, if the user submits a value for the field, the initial value is ignoredentirely. If there is not a user-submitted value (eg, this is the first time we are showing the user the form), if an initialvalue is given, then that is put in the field. If no initial value is provided, the field starts off blank.

Lines 30-32 should be easy enough to understand: they are almost identical to line 29. Returning back to line 28, wesee a call to fieldsToTable. This function displays our form fields as a table, with one field per row. We have afew other options available, but this will most likely be the one you use most.

Moving on to the handler function, on line 36 we call our paramsFormlet function with a Nothing argument:this means the form starts off blank. We pass this value to runFormGet, which binds our form to GET (aka, query-string) parameters. (Before you ask, yes, there is also runFormPost.) The return type of that function is a 3-tuple,containing:

1. The result of validating the form. We see below (lines 39-41) that there are three constructors for aFormResult: FormMissing for when no data was submitted, FormFailure for validation errors, andFormSuccess when everything went OK.

Page 37: Yesod Web Framework Book

OpenTopic | Basics | 37

2. The form itself, as a Widget. This value will include inline validation errors, the previously submitted values,labels, etc. Notably, it does not include the <table> or <form> tags; you can look at the Hamlet template (lines47-52) to see why that is.

3. The value for the enctype attribute of the form. Unless you have file submissions, this will be url-encoded.

Figure 1: Initial state of form

Figure 2: After submitting the form

Something not there to be noticed

Did you notice that at no point did I specify the name attribute for any of those fields? There's actually a bit of magicat play here, so let's unravel it. First: Yesod can automatically provide unique names to forms, and by default it doesthat. Let's have a look at the generated code (newlines and tabbing added for convenience; Hamlet always spits outcondensed code):

<form> <table> <tr> <td><label for="f3">Minimum number</label><div class="tooltip"></div></td> <td><input id="f3" name="f2" type="number" required value=""></td> </tr> <tr> <td><label for="f5">Maximum number</label><div class="tooltip"></div></td> <td><input id="f5" name="f4" type="number" required value=""></td> </tr> <tr> <td><label for="f7">Single word</label><div class="tooltip"></div></td> <td><input id="f7" name="f6" type="text" required value=""></td> </tr> <tr> <td><label for="f9">Plural word</label><div class="tooltip"></div></td> <td><input id="f9" name="f8" type="text" required value=""></td> </tr> <tr> <td colspan="2"><input type="submit" value="Randomize!"></td> </tr> </table></form>

We can see these f2, f3 and so on names and ids have been sprinkled everywhere. This can be incredibly convenient:we never have to worry about coming up with good, unique names. But what if we want to manually specify namesand ids? And additionally, what's up with those tooltip divs?

The answer to both questions is that our original code involved a little bit of a trick. If you look back at line 27, thefirst argument to intField is "Minimum number." But if you look at the API docs for intField, you'll see that the firstargument is something called FormFieldSettings.

To make life easier for you, Yesod defines an IsString instance for FormFieldSettings, and if you turn onOverloadedStrings, you can pretend that the first argument is just a string. But if you want to, you can supply aFormFieldSettings value directly. Case in point, we could replace line 29 with the following:

<*> stringField FormFieldSettings { ffsLabel = "Single word" , ffsTooltip = "The singular version of the object, eg mouse versus mice" , ffsId = Just "single-word" , ffsName = Just "single-word" } (fmap singleWord mparams)

advanced:

Page 38: Yesod Web Framework Book

38 | OpenTopic | Basics

You may have noticed that the minimum and maximumnumber input fields have a type="number" attribute. Thisis an example of Yesod including HTML 5 support bydefault. On some browsers (Chrome, for instance) thisfield gets rendered with up/down arrows to control tovalue, plus it prevents non-numeric input. On browserswithout any specific support, it gets rendered as a plaintype="text" input. diveintohtml5 has a chapter on forms.

Custom fields

Yesod comes built in with a large number of fields, for dates, numbers, booleans, lists, etc. It also provides "maybe"variants for most of these, allowing blank values. However, occassionally you really will want to write your own,custom field. Plus, writing a custom field is a great way to see the internals, so let's hop to.

There's two slightly clumsy things about our previous example: it doesn't give a validation error when the minimumnumber is greater than the maximum, and it would be nice to show both numbers on the same row in the table. Let'sgo ahead and fix both issues.

Just a forewarning: this code looks complicated, but it's really more tedious than anything else. The point of theYesod form library is to alleviate the monotonous tasks involved in web forms. Unfortunately, that tedious work stillhas to happen somewhere.

{-# LANGUAGE QuasiQuotes, TypeFamilies, OverloadedStrings #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}import Yesodimport Yesod.Form.Coreimport System.Randomimport Control.Applicativeimport Safeimport Data.Maybeimport Control.Monadimport Data.Text (Text)import qualified Data.Text as Tdata Rand = Randtype Handler = GHandler Rand RandmkYesod "Rand" [$parseRoutes|/ RootR GET|]instance Yesod Rand where approot _ = ""-- STARTdata Params = Params { numberRange :: (Int, Int) , singleWord :: Text , pluralWord :: Text }

paramsFormlet :: Formlet s m ParamsparamsFormlet mparams = fieldsToTable $ Params <$> rangeField (fmap numberRange mparams) <*> stringField "Single word" (fmap singleWord mparams) <*> stringField "Plural word" (fmap pluralWord mparams)

rangeField :: Maybe (Int, Int) -> GForm s m [FieldInfo s m] (Int, Int)-- same as rangeField :: Maybe (Int, Int) -> FormField s m (Int, Int)-- same as rangeField :: FormletField s m (Int, Int)rangeField initial = GForm $ do minId <- newFormIdent minName <- newFormIdent maxId <- newFormIdent maxName <- newFormIdent env <- askParams let res =

Page 39: Yesod Web Framework Book

OpenTopic | Basics | 39

case env of [] -> FormMissing -- no data provided at all _ -> case (lookup minName env, lookup maxName env) of (Just minString, Just maxString) -> case (readMay $ T.unpack minString, readMay $ T.unpack maxString) of (Just min, Just max) -> if min > max then FormFailure ["Min is greater than max"] else FormSuccess (min, max) _ -> FormFailure ["Expecting two integers"] _ -> FormFailure ["Range is required"] let minValue = fromMaybe "" $ lookup minName env `mplus` fmap (T.pack . show . fst) initial let maxValue = fromMaybe "" $ lookup maxName env `mplus` fmap (T.pack . show . snd) initial let fi = FieldInfo { fiLabel = "Number range" , fiTooltip = "" , fiIdent = minId -- for attribute of the label , fiInput = [$hamlet|Between #<input id="#{minId}" name="#{minName}" type="number" value="#{minValue}"> and #<input id="#{maxId}" name="#{maxName}" type="number" value="#{maxValue}">|] , fiErrors = case res of FormFailure [x] -> Just $ toHtml x _ -> Nothing , fiRequired = True } return (res, [fi], UrlEncoded)-- STOP

getRootR :: Handler RepHtmlgetRootR = do (res, form, enctype) <- runFormGet $ paramsFormlet Nothing output <- case res of FormMissing -> return "Please fill out the form to get a result." FormFailure _ -> return "Please correct the errors below." FormSuccess (Params range single plural) -> do number <- liftIO $ randomRIO range let word = if number == 1 then single else plural return $ T.concat ["You got ", T.pack $ show number, " ", word] defaultLayout $ do addCassius [$cassius|input[type=number] width: 50px.errors color: red|] [$hamlet|\<p>#{output}<form enctype="#{enctype}"> <table> \^{form} <tr> <td colspan="2"> <input type="submit" value="Randomize!">|]

Page 40: Yesod Web Framework Book

40 | OpenTopic | Basics

main = warpDebug 3001 Rand

On line 2 we can see the relevant change to the Params datatype. Technically speaking, we could have left thedatatype the same as before, but that would have made the body of the paramsFormlet function more complicated; Ileave that as an exercise to the reader.

On line 9, we now call the rangeField function (which we are about to define). This function does not take aFormFieldSettings argument; instead, the label, ids and names are all explicitly defined in the rangeField functionitself.

Lines 13-15 give three equivalent type signatures for rangeField. Let's point out a few important pieces:

• FieldInfo is a datatype which allows us to restructure a form in different ways. For example, fieldsToTable is ableto convert this datatype into an HTML table. FieldInfo contains such information as the label, tooltip, HTML ofthe input area and validation errors. We create a value of it on lines 37-51.

• Similar to GHandler and GWidget, GForm is a generic form. This should give a hint to the meaning of the sand m parameters: these are the sub site and master site, respectively. Usually, this is not important. Sometimes,however, we may want to do more fancy things such as loading a list of possible values from a database, in whichcase we will need to pay attention to those parameters.

• And as before, Maybe + Form = Formlet. Additionally, we have the word Field tacked on in lines 14 and 15, toindicate that we have a FieldInfo.

Lines 17-20 acquire our unique ids and names. On line 21, askParams acquires the parameters the user submitted.Since this form gets run eventually with runFormGet, this receives the query-string (GET) parameters; if we usedrunFormPost, it would receive the request body (POST) parameters. Lines 22-34 introduce the tedious part of formchecking; we need to do the following:

1. If there are no form parameters at all, then assume the user didn't submit the form and return a FormMissing (line24).

2. If either of the minName and maxName parameters are missing, return a FormFailure indicating the the range isrequired (line 34).

3. If either of the parameters do not parse an integers, return a FormFailure (line 33).4. Now that we know we have two integers, check if minimum is greater than maximum. If so, return a FormFailure

(line 31). Otherwise, we have a success (line 32).

advanced:

Note that technically we should check in step 2 if the usersubmitted an empty string and consider that the same asnot submitting a parameter at all. To make things a littlesimpler in the example, we ignored this case, since theempty string will be caught by step 3 anyway.

Lines 35 and 36 set up the String "value" attribute for the minimum and maximum fields. The code may look a littletricky, but we essentially do the following:

1. If the use submitted a value, use it.2. Otherwise, use the initial value.3. If there is no initial value, use a blank.

Next we create a FieldInfo value. The one really confusing piece is the value of fiIdent: why did I choose minId andnot maxId? The value of fiIdent gets used when constructing the final HTML, as the value of the "for" attribute on thelabel tag. By setting the value to minId, it means that when a user clicks on the label, the browser will shift focus tothe minimum field, which is probably what we want.

The values for fiInput and fiErrors are, once again, tedious but straight-forward. We can now put both of our inputfields together, and if we created any FormFailure above, we display it in the fiErrors. Line 52 returns our 3-tuple ofform result, FieldInfo and encoding type.

Figure 3: Valid entry

Page 41: Yesod Web Framework Book

OpenTopic | Basics | 41

Figure 4: Invalid entry

Automatic Javascript goodness

After seeing that last example, you may be questioning whether forms are really worth it. Overall, I think that creatinga new field is about as difficult as writing all of the code "from scratch", without the forms library. But once you havethe fields, using them is much simpler than the raw approach. And most of the time, the built in fields are sufficient.

Just to leave you with a good taste in your mouth, let's see an example where the combination of forms and widgetscan give you beautiful forms very easily.

{-# LANGUAGE QuasiQuotes, TypeFamilies, OverloadedStrings #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}import Yesodimport Yesod.Form.Jqueryimport Yesod.Form.Nicimport Data.Timeimport Control.Applicativeimport Data.Listimport Data.Text (Text)import qualified Data.Text as Tdata Js = Jstype Handler = GHandler Js JsmkYesod "Js" [$parseRoutes|/ RootR GET/colors ColorsR GET|]instance Yesod Js where approot _ = ""-- STARTinstance YesodJquery Jsinstance YesodNic Jsdata Survey = Survey { birthday :: Maybe Day , favoriteColor :: Text , aboutMe :: Html }

surveyFormlet msurvey = fieldsToTable $ Survey <$> maybeJqueryDayField def "My birthday" (fmap birthday msurvey) <*> jqueryAutocompleteField ColorsR "Favorite color" (fmap favoriteColor msurvey) <*> nicHtmlField "About me" { ffsId = Just "about-me" } (fmap aboutMe msurvey)

getRootR = do (res, form, enctype) <- runFormGet $ surveyFormlet Nothing let msurvey = case res of FormSuccess x -> Just x _ -> Nothing defaultLayout $ do addCassius [$cassius|#about-me width: 400px height: 300px|] [$hamlet|$maybe survey <- msurvey <h3>Previous Entries $maybe bday <- birthday survey <p>Born on #{show bday} <p>Favorite color: #{favoriteColor survey} #{aboutMe survey}<form enctype="#{enctype}">

Page 42: Yesod Web Framework Book

42 | OpenTopic | Basics

<table> ^{form} <tr> <td colspan="2"> <input type="submit">|]-- STOP

getColorsR = do term <- runFormGet' $ stringInput "term" jsonToRepJson $ jsonList $ map (jsonScalar . T.unpack) $ filter (T.isPrefixOf term) colors where colors = T.words "red orange yellow green blue purple black brown"

main = warpDebug 3001 Js

On lines 1 and 2, we declare two new instances: YesodJquery and YesodNic. These typeclasses allow us to specifywhere to get the relevant Javascript libraries and the attached CSS files. By default, they download them from CDNs.Line 3 starts our Survey datatype. On lines 9 through 14, we set up our surveyFormlet, very similar to our formletfrom the previous two examples. The jqueryAutocompleteField function takes a route as its first argument, of whereto get the autocomplete results. The rest of the example is fairly boilerplate.

What's awesome about this example is that no where did we pull in the Javascript libraries: it was taken care of forus automatically. This is the huge advantage of widgets: we are able to package up some complete functionalityin one module and use it with a single line of code in another. All of the field set up code was also handled for usautomatically.

Figure 5: jQuery date selection

Figure 6: jQuery autocomplete

Figure 7: NIC HTML Editor

advanced:

You may be screaming at your screen right now, "Isn't thatan XSS vulnerability?" Normally, allowing a user to inputarbitrary HTML to a page would be dangerous. However,thanks to the xss-sanitize package, all user input isvalidated and ensured to not have XSS attacks.

Summary

Forms are a complicated part of web programming, involving a lot of monotony. The form library in Yesod can turnthis error-prone process into a nice, declarative experience. Teamed up with widgets, you can even create advancedJavascript-powered UIs without a sweat.

SessionsAs much as possible, RESTful applications should avoid storing state about an interaction with a client. However, itis sometimes unavoidable. Features like shopping carts are the classic example, but other more mundane interactionslike proper login handling can be greatly enhanced by proper usage of sessions.

This chapter will describe how Yesod stores session data, how you can access this data, and some special functions tohelp you make the most of sessions.

Page 43: Yesod Web Framework Book

OpenTopic | Basics | 43

Clientsession

One of the earliest packages spun off from Yesod was clientsession. This package uses encryption andsignatures to store data in a client-side cookie. The encryption prevents the user from tampering with the data, and thesignature ensures that the session cannot be hijacked.

It might sound like a bad idea from an efficiency standpoint to store data in a cookie: after all, this means that the datamust be sent on every request. However, in practice, clientsession can be a great boon for performance.

• No server side database lookup is required to service a request.• We can easily scale horizontally: each request contains all the information we need to send a response.• Production sites should serve their static content from a separate domain name to avoid the overhead of

transmitting the session cookie for each request.

Obviously, storing megabytes of information in the session will be a bad idea. But for that matter, most sessionimplementations recommend against such practices. If you really need massive storage for a user, it is best to simplystore a lookup key in the session, and put the actual data in a database.

Controlling sessions

There are three functions in the Yesod typeclass that control how sessions work. encryptKey returns theencryption key used. By default, it will take this from a local file, so that sessions can persist between databaseshutdowns. This file will be automatically created and filled with random data if it does not exist. And if you overridethis function to return Nothing, sessions will be disabled.

advanced:

Why disable sessions? They do introduce a performanceoverhead. Under normal circumstances, this overheadis minimal, especially compared to database access.However, when dealing with very basic tasks, theoverhead can become noticeable. But be careful aboutdisabling sessions: this will also disable such features asCSRF (Cross-Site Request Forgery) protection.

The next function is clientSessionDuration. This function simply gives the number of minutes that a sessionshould be active. The default is 120 (2 hours).

This value ends up affecting the session cookie in two ways: firstly, it determines the expiration date for the cookieitself. More importantly, however, the session expiration timestamp is encoded inside the session signature. WhenYesod decodes the signature, it checks if the date is in the past; if so, it ignores the session values.

advanced:

Every time Yesod sends a response to the client, itsends an updated session cookie with a new expiredate. This way, even if you do not change the sessionvalues themselves, a session will not time out if the usercontinues to browse your site.

And this leads very nicely to the last function: sessionIpAddress. By default, Yesod also encodes the client's IPaddress inside the cookie to prevent session hijacking. In general, this is a good thing. However, some ISPs are knownfor putting their users behind proxies that rewrite their IP addresses, sometimes changing the source IP in the middleof the session. If this happens, and you have sessionIpAddress enabled, the user's session will be reset. Turning thissetting to false will allow a session to continue under such circumstances, at the cost of exposing a user to sessionhijacking.

Page 44: Yesod Web Framework Book

44 | OpenTopic | Basics

Session Operations

Like most frameworks, sessions in Yesod are simple key-value stores. The base session API boils down to justthree functions: lookupSession gets a value for a key (if available), setSession sets a value for a key, anddeleteSession clears a value for a key.

-- START{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}import Yesodimport Control.Applicative ((<$>), (<*>))

data Session = Sessiontype Handler = GHandler Session SessionmkYesod "Session" [$parseRoutes|/ Root GET POST|]getRoot :: Handler RepHtmlgetRoot = do sess <- getSession hamletToRepHtml [$hamlet|<form method=post <input type=text name=key <input type=text name=val <input type=submit<h1>#{show sess}|]

postRoot :: Handler ()postRoot = do (key, mval) <- runFormPost' $ (,) <$> stringInput "key" <*> maybeStringInput "val" case mval of Nothing -> deleteSession key Just val -> setSession key val liftIO $ print (key, mval) redirect RedirectTemporary Root

instance Yesod Session where approot _ = "" clientSessionDuration _ = 1main = warpDebug 3000 Session

Messages

One usage of sessions previously alluded to is messages. They come to solve a common problem in webdevelopment: the user performs a POST request, the web app makes a change, and then the web app wants tosimultaneously redirect the user to a new page and send the user a success message.

Yesod provides a pair of functions to make this very easy: setMessage stores a value in the session, andgetMessage both reads the value most recently put into the session, and clears the old value so it does notaccidently get displayed twice.

It is recommended to have a call to getMessage in defaultLayout so that any available message is shown to auser immediately, without having to remember to add getMessage calls to every handler.

Ultimate Destination

Not to be confused with a horror film, this concept is used internally in yesod-auth. Simply put, let's say a userrequests a page that requires authentication. Clearly, you need to send them to the login page. A well-designed webapp will then send them back to the first page they requested. That's what we call the ultimate destination.

Page 45: Yesod Web Framework Book

OpenTopic | Basics | 45

redirectUltDest sends the user to the ultimate destination set in his/her session, clearing that value from thesession. It takes a default destination as well, in case there is no destination set. For setting the session, there are threevariants: setUltDest sets the destination to the given type-safe URL, setUltDestString does the same witha text URL, and setUltDest' sets the destination to the currently requested URL.

Summary

The session API in Yesod is very simple. It provides a simple key-value store, and a few convenience functions builton top for common use cases. If used properly, with small payloads, sessions should be an unobtrusive part of yourweb development.

PersistentForms deal with the boundary between the user and the application. Another boundary we need to deal with isbetween the application and the storage layer. Whether it be a SQL database, a YAML file, or a binary blob, odds areyou have to work to get your storage layer to accept your application datatypes. Persistent is Yesod's answer to datastorage- a type-safe universal data store interface for haskell.

Haskell has many different database bindings available. However, most of these have little knowledge of a schemaand therefore do not provide useful static guarantees and force database-dependent interfaces and data structureson the programmer. Haskellers have attempted a more revolutionary route of creating haskell specific data stores toget around these flaws that allow one to easily store any haskell type. These options are great for certain use cases,but they constrain one to the storage techniques provided by the library, do not interface well with other languages,and the flexibility can also mean one must write reams of code for querying data. In contrast, Persistent allows us tochoose among existing databases that are highly tuned for different data storage use cases, interoperate with otherprogramming languages, and to use a safe and productive query interface.

Persistent follows the guiding principles of type safety and concise, declarative syntax. Some other nice features are:

• Database-agnostic. While the most highly supported backends are Postgresql and SQLite, there is alpha supportfor MongoDB.

• By being non-relational in nature, we simultaneously are able to support a wider number of storage layers and arenot constrained by some of the performance bottlenecks incurred through joins.

• A major source of frustration in dealing with SQL databases is changes to the schema. Persistent canautomatically perform database migrations.

Solving the boundary issue

Let's say you are storing information on people in a SQL database. Your table might look something like:

CREATE TABLE Person(id SERIAL PRIMARY KEY, name VARCHAR NOT NULL, age INTEGER)

And if you are using a database like PostgreSQL, you can be guaranteed that the database will never store somearbitrary text in your age field. (The same cannot be said of SQLite, but let's forget about that for now.) To mirror thisdatabase table, you would likely create a Haskell datatype that looks something like:

data Person = Person { personName :: String , personAge :: Int }

It looks like everything is type safe: the database schema matches our Haskell datatypes, the database ensures thatinvalid data can never make it into our data store, and everything is generally awesome. Well, until:

• You want to pull data from the database, and the database layer gives you the data in an untyped format.• You want to find everyone older than 32, and you accidently write "thirtytwo" in your SQL statement. Guess

what: that will compile just fine, and you won't find out you have a problem until runtime.• You decide you want to do something as simple as find the first 10 people alphabetically. No problem... until you

make a typo in your SQL. Once again, you don't find out until runtime.

Page 46: Yesod Web Framework Book

46 | OpenTopic | Basics

In dynamic languages, the answers to these issues is unit testing. For everything that can go wrong, make sure youwrite a test case. But as I am sure you are aware by now, that doesn't jive well with the Yesod approach to things. Welike to take advantage of Haskell's strong typing to save us wherever possible, and data storage is no exception.

So the question remains: how can we use Haskell's type system to save the day?

Types

Like routing, there is nothing intrinsically difficult about type-safe data access. It just requires a lot of monotonous,error prone, boiler plate code. This is a perfect use case for some Template Haskell to generate this code for usautomatically. To start off with, let's analyze some data types and type classes.

PersistValue is the basic building block of Persistent. It is a very simple datatype that can represent data that getssent to and from a database. Its definition is:

data PersistValue = PersistText Text | PersistByteString ByteString | PersistInt64 Int64 | PersistDouble Double | PersistBool Bool | PersistDay Day | PersistTimeOfDay TimeOfDay | PersistUTCTime UTCTime | PersistNull | PersistList [PersistValue] | PersistMap [(T.Text, PersistValue)] | PersistForeignKey ByteString -- ^ intended especially for MongoDB backend

Each Persistent backend needs to know how to translate the relevant values into something the database canunderstand. However, it would be awkward do have to express all of our data simply in terms of these basic types.The next layer is the PersistField typeclass, which defines how an arbitrary Haskell datatype can be marshaledto and from a PersistValue. A PersistField correlates to a column in a SQL database. In our person exampleabove, name and age would be our Persistfields.

To tie up the user side of the code, our last typeclass is PersistEntity. An instance of PersistEntity correlateswith a table in a SQL database. This typeclass defines a number of functions and some associated types.

Code Generation

In order to ensure that the PersistEntity instances match up properly with your Haskell datatypes, Persistent takesresponsibility for both. This is also good from a DRY (DRY) perspective: you only need to define your entities once.Let's see a quick example:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.THimport Database.Persist.Sqlite

mkPersist [$persist|Person name String age Int|]-- STOPmain = return ()

We use a combination of Template Haskell and Quasi-Quotation (like when defining routes): persist is a quasi-quoterwhich converts a whitespace-sensitive syntax into a list of entity definitions. mkPersist takes that list of entities anddeclares:

• One Haskell datatype for each entity.

Page 47: Yesod Web Framework Book

OpenTopic | Basics | 47

• A PersistEntity instance for each datatype defined.Of course, the interesting part is how to use this datatype once it is defined.

{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.THimport Database.Persist.Sqliteimport Control.Monad.IO.Class (liftIO)

mkPersist [$persist|Person name String age Int|]-- STARTmain = withSqliteConn ":memory:" $ runSqlConn $ do michaelId <- insert $ Person "Michael" 26 michael <- get michaelId liftIO $ print michael-- STOP

We start off with some standard database connection code. In this case, we used the single-connection functions.Persistent also comes built in with connection pool functions, which we will generally want to use in production.

In this example, we have seen two functions: insert creates a new record in the database and returns its ID. Likeeverything else in Persistent, IDs are type safe. Each PersistEntity has an associated type called Key. So when youcall insert $ Person "Michael" 25, it gives you a value back of type Key Person.

Note: In order to make code a bit shorter, and to help withdeclaring routes and foreign keys (discussed later), everykey gets a one-word type synonym created as well. In thecase of person, it would be type PersonId = KeyPerson.

The next function we see is get, which attempts to load a value from the database using a Key. In Persistent, younever need to worry that you are using the key from the wrong table: trying to load up a different entity (like House)using a Key Person will never compile.

PersistBackend

One last detail is left unexplained from the previous example: what are those withSqliteConn and runSqlConnfunctions doing, and what is that monad that our database actions are running in?

All database actions need to occur within an instance of PersistBackend. As its name implies, every backend(PostgreSQL, SQLite, MongoDB) defines its own instance of PersistBackend. This is where all the translations fromPersistValue to database-specific values occur, where SQL query generation happens, and so on.

advanced:

As you can imagine, even though PersistBackendprovides a safe, well-typed interface to the outside world,there are a lot of database interactions that could gowrong. However, by testing this code automatically andthoroughly in a central location, we can centralize ourerror-prone code into a single location and make sure it isas bug-free as possible.

withSqliteConn creates a single connection to a database using its supplied connection string. For our test cases, wewill use ":memory:", which simply uses an in-memory database. runSqlConn uses that connection to run the inneraction, in this case, SqlPersist. Both SQLite and PostgreSQL share the same instance of PersistBackend.

One important thing to note is that everything which occurs inside a single call to runSqlConn runs in a singletransaction. This has two important implications:

Page 48: Yesod Web Framework Book

48 | OpenTopic | Basics

• For many databases, committing a transaction can be a costly activity. By putting multiple steps into a singletransaction, you can speed up code dramatically.

• If an exception is thrown anywhere inside a single call to runSqlConn, all actions will be rolled back.

Migrations

I'm sorry to tell you, but so far I have lied to you a bit: the example from the previous section does not actually work.If you try to run it, you will get an error message about a missing table.

For SQL databases, one of the major pains can be managing schema changes. Instead of leaving this to the user,Persistent steps in to help, but you have to ask it to help. Let's see what this looks like:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.THimport Database.Persist.Sqliteimport Control.Monad.IO.Class (liftIO)

mkPersist [$persist|Person name String age Int|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration $ migrate (undefined :: Person) -- this line added: that's it! michaelId <- insert $ Person "Michael" 26 michael <- get michaelId liftIO $ print michael-- STOP

With this one little code change, Persistent will automatically create your Person table for you. This split betweenrunMigration and migrate allows you to migrate multiple tables simultaneously.

This works when dealing with just a few entities, but can quickly get tiresome once we are dealing with a dozenentities. Instead of repeating yourself, Persistent provides a helper function:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.TH

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name String age IntCar color String make String model String|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll-- STOP

mkMigrate is a Template Haskell function which creates a new function that will automatically call migrate on allentities defined in the persist block. The share function is just a little helper that passes the information from thepersist block to each Template Haskell function and concatenates the results.

Page 49: Yesod Web Framework Book

OpenTopic | Basics | 49

Persistent has very conservative rules about what it will do during a migration. It starts by loading up tableinformation from the database, complete with all defined SQL datatypes. It then compares that against the entitydefinition given in the code. For simple cases, it will automatically alter the schema:

• The datatype of a field changed. However, the database may object to this modification if the data cannot betranslated.

• A field was added. However, if the field is not null, no default value is supplied (we'll discuss defaults later) andthere is already data in the database, the database will not allow this to happen.

• A field is converted from not null to null. In the opposite case, Persistent will attempt the conversion, contingentupon the database's approval.

• A brand new entity is added.

However, there are a number of cases that Persistent will not handle:

• Field or entity renames: Persistent has no way of knowing that "name" has now been renamed to "fullName": all itsees is an old field called name and a new field called fullName.

• Field removals: since this can result in data loss, Persistent by default will refuse to perform the action (you canforce the issue by using runMigrationUnsafe instead of runMigration, though it is not recommended).

runMigration will print out the migrations it is running on stderr (you can bypass this by using runMigrationSilent).Whenever possible, it uses ALTER TABLE calls. However, in SQLite, ALTER TABLE has very limited abilities,and therefore Persistent must resort to copying the data from one table to another.

Finally, if instead of performing a migration, you just want Persistent to give you hints about what migrations arenecessary, use the printMigration function. This function will print out the migrations which runMigration wouldperform for you. This may be useful for performing migrations that Persistent is not capable of, for adding arbitrarySQL to a migration, or just to log what migrations occurred.

advanced:

Although there is no official Persistent integration, there isa haskell package that can assist with running migrationscalled dbmigrations.

Attributes

So far, we have seen a very simple syntax for our persist blocks: a line for the name of our entities, and then anindented line for each field with two words: the name of the field and the datatype of the field. Persistent handlesmore than this: you can assign an arbitrary list of attributes after the first two words on a line.

Let's say that we want to add two new fields to our Person entity: a favorite color (optional), and the timestamp ofwhen he/she was added to the system. For entities already in the database, we want to just use the current date-timefor that timestamp.

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Time

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name String age Int color String Maybe created UTCTime default=now()|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll-- STOP

Page 50: Yesod Web Framework Book

50 | OpenTopic | Basics

Maybe is one of many built in, single word attributes. We will see many more below. The default attribute isbackend specific, and uses whatever syntax is understood by the database. In this case, it uses the database's built-inCURRENT_TIMESTAMP function. Let's say we now want to add a field for favorite programming language:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Time

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name String age Int color String Maybe created UTCTime default=now() language String default='Haskell'|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll-- STOP

Note: The default attribute has absolutely no impacton the Haskell code itself; you still need to fill in allvalues. This will only affect the database schema andautomatic migrations.

We need to surround the string with single quotes so that the database can properly interpret it. Finally, Persistent canuse double quotes for containing white space, so let's say we want to set someone's default home country to the ElSalvador:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Time

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name String age Int color String Maybe created UTCTime default=now() language String default='Haskell' country String "default='El Salvador'"|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll-- STOP

Associated Types

We saw above that each PersistEntity has a Key associated type to provide for type-safe lookups by numerical ID.But let's say we are not satisfied with looking up by ID: I want to get every person from my database with the name"Michael" who is older than 25. In SQL, there's no type-safe way to do this. In Persistent, we can do it easily.

-- START

Page 51: Yesod Web Framework Book

OpenTopic | Basics | 51

{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Timeimport Control.Monad.IO.Class (liftIO)

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name String Eq age Int Gt|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll michaels <- selectList [ PersonNameEq "Michael" , PersonAgeGt 25 ] [] 0 0 -- we will explain these later, all in good time liftIO $ print michaels-- STOP

The first thing to notice is that we have added some extra attributes to our persist block: Eq for name and Gt for age.There are six filtering attributes:

• Eq- equals• Ne- not equals• Lt- less than• Le- less than or equals• Gt- greater than• Ge- greater than or equals• In- equals any member of a list (like a SQL IN)

By adding these attribute, mkPersist automatically adds corresponding data constructors to the Filter associatedtype. In our case, this comes out to the equivalent of

data Filter Person = PersonNameEq String | PersonAgeGt Int

Notice how the datatype of the field is encoded in the data constructor itself. This is the very heart of Persistent's typesafety. It's impossible to type in PersonAgeGt "twenty-five", the compiler just won't have it.

Similarly, Persistent uses the Order associated type for sorting results. This comes with two attributes: Asc forsorting in ascending order, and Desc for descending order. So assuming we want to get all Michaels older than 25 indescending chronological order:

{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Timeimport Control.Monad.IO.Class (liftIO)

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name String Eq age Int Gt Desc|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll-- START michaels <- selectList

Page 52: Yesod Web Framework Book

52 | OpenTopic | Basics

[ PersonNameEq "Michael" , PersonAgeGt 25 ] [ PersonAgeDesc ] 0 0 -- we will explain these later, all in good time-- STOP liftIO $ print michaels

Finally, let's say we have decided to rename everyone older than 25 and named Michael to Mike. For this, we use theUpdate associated type:

{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Time

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name String Eq Update age Int Gt|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll-- START updateWhere [ PersonNameEq "Michael" , PersonAgeGt 25 ] [ PersonName "Mike" ]-- STOP

There are a number of functions which take these associated types as parameters. As usual, the Haddockdocumentation is always the most authoritative source for a complete lists of API functions.

Uniqueness

The final associated type is is Unique. Its usage is slightly different than that of its siblings:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Timeimport Control.Monad.IO.Class (liftIO)

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person firstName String lastName String age Int Gt Desc PersonName firstName lastName|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll insert $ Person "Michael" "Snoyman" 26 michael <- getBy $ PersonName "Michael" "Snoyman" liftIO $ print michael-- STOP

Page 53: Yesod Web Framework Book

OpenTopic | Basics | 53

To declare a unique combination of fields, we add an extra line to our declaration. Persistent knows that it is defininga unique constructor, since the line begins with a capital letter. Each following word must be a field in this entity.

The main restriction on uniqueness is that it can only be applied non-null fields. The reason for this is that the SQLstandard is ambiguous on how uniqueness should be applied to NULL (eg, is NULL=NULL true or false?). Besidesthat ambiguity, most SQL engines in fact implement rules which would be contrary to what the Haskell datatypesanticipate (eg, PostgreSQL says that NULL=NULL is false, whereas Haskell says Nothing == Nothing is True).

Relations

Persistent allows references between your data types in a manner that is consistent with supporting non-SQLdatabases. We do this by embedding a Key value in the related entity. So if a person has many cars:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Control.Monad.IO.Class (liftIO)import Data.Time

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name StringCar owner PersonId Eq name String|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll bruce <- insert $ Person "Bruce Wayne" insert $ Car bruce "Bat Mobile" insert $ Car bruce "Porsche" -- this could go on a while cars <- selectList [CarOwnerEq bruce] [] 0 0 liftIO $ print cars-- STOP

advanced:

You might be wondering what that PersonId is.Persistent automatically defines a type synonym typePersonId = Key Person for all of your entities.

Using this technique, it's very easy to define one-to-many relationships. To define many-to-many relationships, weneed a join entity, which has a one-to-many relationship with each of the original tables. It is also a good idea touse uniqueness constraints on these. For example, to model a situation where we want to track which people haveshopped in which stores:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Time

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name StringStore name String

Page 54: Yesod Web Framework Book

54 | OpenTopic | Basics

PersonStore person PersonId store StoreId UniquePersonStore person store|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll

bruce <- insert $ Person "Bruce Wayne" michael <- insert $ Person "Michael"

target <- insert $ Store "Target" gucci <- insert $ Store "Gucci" sevenEleven <- insert $ Store "7-11"

insert $ PersonStore bruce gucci insert $ PersonStore bruce sevenEleven

insert $ PersonStore michael target insert $ PersonStore michael sevenEleven-- STOP

Custom Fields

Occassionally, you will want to define a custom field to be used in your datastore. The most common case is anenumeration, such as employment status. For this, Persistent provides a helper Template Haskell function:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell, OverloadedStrings #-}import Database.Persistimport Database.Persist.Sqliteimport Database.Persist.THimport Data.Time

data Employment = Employed | Unemployed | Retired deriving (Show, Read, Eq)derivePersistField "Employment"

share [mkPersist, mkMigrate "migrateAll"] [$persist|Person name String employment Employment|]

main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll

insert $ Person "Bruce Wayne" Retired insert $ Person "Peter Parker" Unemployed insert $ Person "Michael" Employed-- STOP

derivePersistField stores the data in the database using a string field, and performs marshaling using the Show andRead instances of the datatype. This may not be as efficient as storing via an integer, but it is much more future proof:even if you add extra constructors in the future, your data will still be valid.

Persistent: Raw SQL

The Persistent packages provides a type safe interface to data stores. It tries to be backend-agnostic, such as notrelying on relational features of SQL. My experience has been you can easily perform 95% of what you need to dowith the high-level interface. (In fact, most of my web apps use the high level interface exclusively.)

Page 55: Yesod Web Framework Book

OpenTopic | Basics | 55

But occassionally you'll want to use a feature that's specific to a backend. One feature I've used in the past is full textsearch. In this case, we'll use the SQL "LIKE" operator, which is not modeled in Persistent. We'll get all people withthe last name "Snoyman" and print the records out.

{-# LANGUAGE OverloadedStrings #-}{-# LANGUAGE TemplateHaskell #-}{-# LANGUAGE QuasiQuotes #-}{-# LANGUAGE TypeFamilies #-}{-# LANGUAGE GeneralizedNewtypeDeriving #-}import Database.Persist.Sqlite (withSqliteConn)import Database.Persist.TH (mkPersist, persist, share, mkMigrate)import Database.Persist.GenericSql (runSqlConn, runMigration, SqlPersist)import Database.Persist.GenericSql.Raw (withStmt)import Database.Persist.GenericSql.Internal (RowPopper)import Data.Text (Text)import Database.Persistimport Control.Monad.IO.Class (liftIO)

share [mkPersist, mkMigrate "migrateAll"] [persist|Person name Text|]

main :: IO ()main = withSqliteConn ":memory:" $ runSqlConn $ do runMigration migrateAll insert $ Person "Michael Snoyman" insert $ Person "Miriam Snoyman" insert $ Person "Eliezer Snoyman" insert $ Person "Gavriella Snoyman" insert $ Person "Greg Weber" insert $ Person "Rick Richardson"

-- Persistent does not provide the LIKE keyword, but we'd like to get the -- whole Snoyman family... let sql = "SELECT name FROM Person WHERE name LIKE '%Snoyman'" withStmt sql [] withPopper

-- A popper returns one row at a time. We loop over it until it returns Nothing.withPopper :: RowPopper (SqlPersist IO) -> SqlPersist IO ()withPopper popper = loop where loop = do mrow <- popper case mrow of Nothing -> return () Just row -> liftIO (print row) >> loop

FIXME Outline

• mkToForm• Advanced: select enumerator interface• raw SQL• Join modules• custom table/column names

Page 56: Yesod Web Framework Book

56 | OpenTopic | Advanced

Advanced

RESTful ContentOne of the stories from the early days of the web is how search engines wiped out entire websites. When dynamicweb sites were still a new concept, developers didn't appreciate the difference between a GET and POST request. Asa result, they created pages- accessed with the GET method- that would delete pages. When search engines startedcrawling these sites, they could wipe out all the content.

If these web developers had followed the HTTP spec properly, this would not have happened. A GET requestis supposed to cause no side effects (you know, like wiping out a site). Recently, there has been a move in webdevelopment called Representational State Transfer, also known as REST. This chapter describes the RESTfulfeatures in Yesod and how you can use them to create more robust web applications.

Request methods

In many web frameworks, you write one handler function per resource. In Yesod, the default is to have a separatehandler function for each request method. The two most common request methods you will deal with in creatingweb sites are GET and POST. These are the most well-supported methods in HTML, since they are the only onessupported by web forms. However, when creating RESTful APIs, the other methods are very useful.

Technically speaking, you can create whichever request methods you like, but it is strongly recommended to stick tothe ones spelled out in the HTTP spec. The most common of these are:

GET Read-only requests. Assuming no other changesoccur on the server, calling a GET request multipletimes should result in the same response, barringsuch things as "current time" or randomly assignedresults.

POST A general mutating request. A POST requestshould never be submitted twice by the user. Acommon example of this would be to transferfunds from one bank account to another.

PUT Create a new resource on the server, or replacean existing one. This method is safe to be calledmultiple times.

DELETE Just like it sounds: wipe out a resource on theserver. Calling multiple times should be OK.

To a certain extent, this fits in very well with Haskell philosophy: a GET request is similar to a pure function, whichcannot have side effects. Of course, in practice, your GET functions will probably perform IO, such as readinginformation from a database, logging user actions, and so on.

See the routing and handlers chapter chapter for more information on the syntax of defining handler functions foreach request method.

Representations

Let's say we have a Haskell datatype and value:

data Person = Person { name :: String, age :: Int }michael = Person "Michael" 25

Page 57: Yesod Web Framework Book

OpenTopic | Advanced | 57

We could represent that data as HTML:

<table> <tr> <th>Name</th> <td>Michael</td> </tr> <tr> <th>Age</th> <td>25</td> </tr></table>

or we could represent it as JSON:

{"name":"Michael","age":25}

or as XML:

<person> <name>Michael</name> <age>25</age></person>

Often times, web applications will use a different URL to get each of these representations; perhaps /person/michael.html, /person/michael.json, etc. Yesod follows the RESTful principle of a single URL for each resource. Soin Yesod, all of these would be accessed from /person/michael.

Then the question becomes how do we determine which representation to serve. The answer is the HTTP Acceptheader: it gives a prioritized list of content types the client is expecting. Yesod will automatically determine whichrepresentation to serve based upon this header.

Let's make that last sentence a bit more concrete with some code:

class HasReps a where chooseRep :: a -> [ContentType] -> IO (ContentType, Content)

The chooseRep function takes two arguments: the value we are getting representations for, and a list of content typesthat the client will accept. We determine this by reading the "Accept" request header. chooseRep returns a tuplecontaining the content type of our response and the actual content.

This typeclass is the core of Yesod's RESTful approach to representations. Every handler function must return aninstance of HasReps. Yesod provides a number of instances of HasReps out of the box. When we use defaultLayout,for example, the return type is RepHtml, which looks like:

newtype RepHtml = RepHtml Contentinstance HasReps RepHtml where chooseRep (RepHtml content) _ = return ("text/html", content)

What's interesting here is that we ignore entirely the list of expected content types. A number of the built inrepresentations (RepHtml, RepPlain, RepJson, RepXml) in fact only support a single representation, and thereforewhat the client requests in the Accept header is irrelevant.

RepHtmlJson

An example to the contrary is RepHtmlJson, which provides either an HTML or JSON representation. This instancehelps greatly in programming AJAX applications that degrade nicely. Here is an example that returns either HTML orJSON data, depending on what the client wants.

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, OverloadedStrings #-}{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}import Yesoddata R = RmkYesod "R" [$parseRoutes|/ RootR GET

Page 58: Yesod Web Framework Book

58 | OpenTopic | Advanced

/#String NameR GET|]instance Yesod R where approot _ = ""

getRootR = defaultLayout $ do setTitle "Homepage" addScriptRemote "http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js" addJulius [$julius|$(function(){ $("a").click(function(){ jQuery.getJSON($(this).attr("href"), function(o){ $("div").text(o.name); }); return false; });});|] let names = words "Larry Moe Curly" addHamlet [$hamlet|<div id="results"> Your results will be placed here if you have Javascript enabled.<ul> $forall name <- names <li> <a href="@{NameR name}">#{name}|]

getNameR name = do let widget = do setTitle $ string name addHamlet [$hamlet|Looks like you have Javascript off. Name: #{name}|] let json = jsonMap [("name", jsonScalar name)] defaultLayoutJson widget json

main = warpDebug 4000 R-- STOP

Our getRootR handler creates a page with three links and some Javascript which intercept clicks on the links andperforms asynchronous requests. If the user has Javascript enabled, clicking on the link will cause a request to be sentwith an "Accept" header of "application/json". In that case, getNameR will return the JSON representation defined online 40.

If the user disables Javascript, clicking on the link will send the user to the appropriate URL. A web browser placespriority on an HTML representation of the data, and therefore the page defined on lines 36-38 will be returned.

We can of course extend this to work with XML, Atom feeds, or even binary representations of the data. A funexercise could be writing a web application that serves data simply using the default Show instances of datatypes, andthen writing a web client that parses the results using the default Read instances.

News Feeds

A great, practical example of multiple representations if the yesod-newsfeed package. There are two majorformats for news feeds on the web: RSS and Atom. They contain almost exactly the same information, but are justpackaged up differently.

The yesod-newsfeed package defines a Feed datatype which contains information like title, description, and lastupdated time. It then provides two separate sets of functions for displaying this data: one for RSS, one for Atom. Theyeach define their own representation datatypes:

newtype RepAtom = RepAtom Contentinstance HasReps RepAtom where

Page 59: Yesod Web Framework Book

OpenTopic | Advanced | 59

chooseRep (RepAtom c) _ = return (typeAtom, c)newtype RepRss = RepRss Contentinstance HasReps RepRss where chooseRep (RepRss c) _ = return (typeRss, c)

But there's a third module which defines another datatype:

data RepAtomRss = RepAtomRss RepAtom RepRssinstance HasReps RepAtomRss where chooseRep (RepAtomRss (RepAtom a) (RepRss r)) = chooseRep [ (typeAtom, a) , (typeRss, r) ]

This datatype will automatically serve whichever representation the client prefers, defaulting to Atom. If a clientconnects that only understands RSS, assuming it provides the correct HTTP headers, Yesod will provide RSS output.

Other request headers

There are a great deal of other request headers available. Some of them simply affect the transfer of data between theserver and client, and should not affect the application at all. For example, Accept-Encoding informs the server whichcompression schemes the client understands, and Host informs the server which virtual host to serve up.

Other headers do affect the application, but are automatically read by Yesod. For example, the Accept-Languageheader specifies which human language (English, Spanish, German, Swiss-German) the client prefers. See the i18nchapter for details on how this header is used.

Stateless

I've saved this section for the last, not because it is less important, but rather because there are no specific features inYesod to enforce this.

HTTP is a stateless protocol: each request is to be seen as the beginning of a conversation. This means, for instance,it doesn't matter to the server if you requested five pages previously, it will treat your sixth request as if it's your firstone.

On the other hand, some features on websites won't work without some kind of state. For example, how can youimplement a shopping cart without saving information about items in between requests?

The solution to this is cookies, and built on top of this, sessions. We have a whole section addressing the sessionsfeatures in Yesod. However, I cannot stress enough that this should be used sparingly.

Let me give you an example. There's a popular bug tracking system that I deal with on a daily basis which horriblyabuses sessions. There's a little drop-down on every page to select the current project. Seems harmless, right? Whatthat dropdown does is set the current project in your session.

The result of all this is that clicking on the "view issues" link is entirely dependent on the last project you selected.There's no way to create a bookmark to your "Yesod" issues and a separate link for your "Hamlet" issues.

The proper RESTful approach to this is to have one resource for all of the Yesod issues and a separate one for all theHamlet issues. In Yesod, this is easily done with a route definition like:

/ ProjectsR GET/projects/#ProjectID ProjectIssuesR GET/issues/#IssueID IssueR GET

Be nice to your users: proper stateless architecture means that basic features like bookmarks, permalinks and theback/forward button will always work.

Summary

Yesod adheres to the following tenets of REST:

• Use the correct request method.• Each resource should have precisely one URL.

Page 60: Yesod Web Framework Book

60 | OpenTopic | Advanced

• Allow multiple representations of data on the same URL.• Inspect request headers to determine extra information about what the client wants.This makes it easy to use Yesod not just for building websites, but for building APIs. In fact, using techniques suchas RepHtmlJson, you can serve both a user-friendly, HTML page and a machine-friendly, JSON page from the sameURL.

Authentication and AuthorizationThis chapter is just an outline. It will contain the sections:• Authentication• Authorization• Auth stuff (isAuthorized, isWriteRequest, authRoute)

Note: Logout does not invalidate session.

Scaffolding and the Site Template• How to scaffold: yesod executable• File structure layout• Some special features for static files (addStaticContent, urlRenderOverride)

Advanced FormsThis chapter is just an outline. It will contain the sections:• GFormMonad• Inner workings of GForm• Meaning of the 5 type aliases• Custom form layout• Multiple rows (still have to figure out how to program that!)Include information from blog post.

http://www.haskell.org/pipermail/web-devel/2010/000507.html

TestingFIXME This is just an outline. This chapter will mostly describe how to use wai-test.

Sending EmailSorry, no information yet. This will mostly be about the mime-mail package.

Deploying your WebappI can't speak for others, but I personally prefer programming to system administration. But the fact is that, eventually,you need to serve your app somehow, and odds are that you'll need to be the one to set it up.

There are some promising initiatives in the Haskell web community towards making deployment easier. In the future,we may even have a service that allows you to deploy your app with a single command.

But we're not there yet. And even if we were, such a solution will never work for everyone. This chapter covers thedifferent options you have for deployment, and gives some general recommendations on what you should choose indifferent situations.

Page 61: Yesod Web Framework Book

OpenTopic | Advanced | 61

Warp

As we have mentioned before, Yesod is built on the Web Application Interface (WAI), allowing it to run on any WAIbackend. At the time of writing, the following backends are available:• Warp• FastCGI• SCGI• CGI• Webkit• Development serverThe last two are not intended for production deployments. Of the remaining four, all can be used for productiondeployment in theory. In practice, a CGI backend will likely be horribly inefficient, since a new process must bespawned for each connection. And SCGI is not nearly as well supported by frontend web server as Warp or FastCGI.

So between the two remaining choices, Warp gets a very strong recommendation because:• It is significantly faster.• Like FastCGI, it can run behind a frontend server like Nginx, using reverse HTTP proxy.• In addition, it is a fully capable server of its own accord, and can therefore be used without any frontend server.But as fast as Warp is, it is still optimized as an application server, not a static file server. Therefore, for bestperformance, we recommend using Warp as your WAI backend, and to reverse proxy it behind Nginx, which isprobably the fastest web server available today.

Configuration

In general, Nginx will listen on port 80 and your Yesod/Warp app will listen on some unprivileged port (lets say4321). You will then need to provide a nginx.conf file, such as:

daemon off; # Don't run nginx in the background, good for monitoring appsevents { worker_connections 4096;}

http { server { listen 80; # Incoming port for Nginx server_name www.myserver.com; location / { proxy_pass http://127.0.0.1:4321; # Reverse proxy to your Yesod app } }}

You can add as many server blocks as you like. A common addition is to ensure users always access your pages withthe www prefix on the domain name, ensuring the RESTful principle of canonical URLs. (You could just as easily dothe opposite and always strip the www, just make sure that your choice is reflected in both the nginx config and theapproot of your site.) In this case, we would add the block:

server { listen 80; server_name myserver.com; rewrite ^/(.*) http://www.myserver.com/$1 permanent;}

A highly recommended optimization is to server static files from a separate domain name, therefore bypassing thecookie transfer overhead. Assuming that our static files are stored in the static folder within our site folder, and thesite folder is located at /home/michael/sites/mysite, this would look like:

server { listen 80; server_name static.myserver.com;

Page 62: Yesod Web Framework Book

62 | OpenTopic | Advanced

root /home/michael/sites/mysite/static; expires max; # Take advantage of yesod-static's content hash query strings}

In order for this to work, your site must properly rewrite static URLs to this alternate domain name. The scaffoldedsite is set up to make this fairly simple via the Settings.staticroot function and the definition of urlRenderOverride.However, if you just want to get the benefit of nginx's faster static file serving without dealing with separate domainnames, you can instead modify your original server block like so:

server { listen 80; # Incoming port for Nginx server_name www.myserver.com; location / { proxy_pass http://127.0.0.1:4321; # Reverse proxy to your Yesod app } location /static { root /home/michael/sites/mysite; # Notice that we do *not* include /static expires max; }}

Server Process

Many people are familiar with an Apache/mod_php or Lighttpd/FastCGI kind of setup, where the web serverautomatically spawns the web application. With nginx, either for reverse proxying or FastCGI, this is not the case:you are responsible to run your own process. I strongly recommend a monitoring utility which will automaticallyrestart your application in case it crashes. There are many great options out there, such as daemontools.

To give a concrete example, here is an Upstart config file. The file must be placed in /etc/init/mysite.conf:

description "My awesome Yesod application"start on runlevel [2345];stop on runlevel [!2345];respawnchdir /home/michael/sites/mysiteexec /home/michael/sites/mysite/dist/build/mysite/mysite

Once this is in place, bringing up your application is as simple as sudo start mysite.

FastCGI

Some people may prefer using FastCGI for deployment. In this case, you'll need to add an extra tool to the mix.FastCGI works by receiving new connection from a file descriptor. The C library assumes that this file descriptorwill be 0 (standard input), so you need to use the spawn-fcgi program to bind your aplication's standard input to thecorrect socket.

It can be very convenient to use Unix named sockets for this instead of binding to a port, especially when hostingmultiple applications on a single host. A possible script to load up your app could be:

spawn-fcgi \ -d /home/michael/sites/mysite \ -s /tmp/mysite.socket \ -n \ -M 511 -u michael -- /home/michael/sites/mysite/dist/build/mysite-fastcgi/mysite-fastcgi

You will also need to configure your frontend server to speak to your app over FastCGI. This is relatively painless inNginx:

server { listen 80; server_name www.myserver.com; location / {

Page 63: Yesod Web Framework Book

OpenTopic | Advanced | 63

fastcgi_pass unix:/tmp/mysite.socket; }}

That should look pretty familiar from above. The only last trick is that, with Nginx, you need to manually specify allof the FastCGI variables. It is recommended to store these in a separate file (say, fastcgi.conf) and then add includefastcgi.conf; to the end of your http block. The contents of the file, to work with WAI, should be:

fastcgi_param QUERY_STRING $query_string;fastcgi_param REQUEST_METHOD $request_method;fastcgi_param CONTENT_TYPE $content_type;fastcgi_param CONTENT_LENGTH $content_length;fastcgi_param PATH_INFO $fastcgi_script_name;fastcgi_param SERVER_PROTOCOL $server_protocol;fastcgi_param GATEWAY_INTERFACE CGI/1.1;fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;fastcgi_param REMOTE_ADDR $remote_addr;fastcgi_param SERVER_ADDR $server_addr;fastcgi_param SERVER_PORT $server_port;fastcgi_param SERVER_NAME $server_name;

Desktop

Another nifty backend is wai-handler-webkit. This backend combines Warp and QtWebkit to create anexecutable that a user simply double-clicks. This can be a convenient way to provide an offline version of yourapplication.

One of the very nice conveniences of Yesod for this is that your templates are all compiled into the executable, andthus do not need to be distributed with your application. Static files do, however.

Note: Earlier versions of Yesod allowed for embeddingof static files in the executable as well. This is not aparticularly complicated feature to implement, and if thereis demand, can be added back in the future.

CGI on Apache

CGI and FastCGI work almost identically on Apache, so it should be fairly straight-forward to port this configuration.You essentially need to accomplish two goals:

1. Get the server to serve your file as (Fast)CGI.2. Rewrite all requests to your site to go through the (Fast)CGI executable.

Here is a configuration file for serving a blog application, with an executable named "bloggy.cgi", living in asubfolder named "blog" of the document root. This example was taken from an application living in the path /f5/snoyman/public/blog.

Options +ExecCGIAddHandler cgi-script .cgiOptions +FollowSymlinks

RewriteEngine OnRewriteRule ^/f5/snoyman/public/blog$ /blog/ [R=301,S=1]RewriteCond $1 !^bloggy.cgiRewriteCond $1 !^static/RewriteRule ^(.*) bloggy.cgi/$1 [L]

The first RewriteRule is to deal with subfolders. In particular, it redirects a request for /blog to /blog/. The firstRewriteCond prevents directly requesting the executable, the second allows Apache to serve the static files, and thelast line does the actual rewriting.

Page 64: Yesod Web Framework Book

64 | OpenTopic | Advanced

FastCGI on lighttpd

For this example, I've left off some of the basic FastCGI settings like mime-types. I also have a more complex file inproduction that prepends "www." when absent and serves static files from a separate domain. However, this shouldserve to show the basics.

Here, "/home/michael/fastcgi" is the fastcgi application. The idea is to rewrite all requests to start with "/app", andthen serve everything beginning with "/app" via the FastCGI executable.

server.port = 3000server.document-root = "/home/michael"server.modules = ("mod_fastcgi", "mod_rewrite")

url.rewrite-once = ( "(.*)" => "/app/$1")

fastcgi.server = ( "/app" => (( "socket" => "/tmp/test.fastcgi.socket", "check-local" => "disable", "bin-path" => "/home/michael/fastcgi", # full path to executable "min-procs" => 1, "max-procs" => 30, "idle-timeout" => 30 )))

CGI on lighttpd

This is basically the same as the FastCGI version, but tells lighttpd to run a file ending in ".cgi" as a CGI executable.In this case, the file lives at "/home/michael/myapp.cgi".

server.port = 3000server.document-root = "/home/michael"server.modules = ("mod_cgi", "mod_rewrite")

url.rewrite-once = ( "(.*)" => "/myapp.cgi/$1")

cgi.assign = (".cgi" => "")

InternationalizationYesod does not currently provide a prebuilt internationalization solution such as gettext. Instead, it provides somebasic functionality for determining the user's language preference and leaves the rest to you. This chapter will givean example of how to use types to deal with i18n. We will additionally cover how to return responses in a characterencoding besides UTF8.

This chapter, however, has not yet been written. Please check back later.

Creating a SubsiteHow many sites provide authentication systems? Or need to provide CRUD (CRUD) management of some objects?Or a blog? Or a wiki?

The theme here is that many websites include common components that can be reused throughout multiple sites.However, it is often quite difficult to get code to be modular enough to be truly plug-and-play: a component will

Page 65: Yesod Web Framework Book

OpenTopic | Advanced | 65

require hooks into the routing system, usually for multiple routes, and will need some way of sharing stylinginformation with the master site.

In Yesod, the solution is subsites. A subsite is a collection of routes and their handlers that can be easily inserted intoa master site. By using type classes, it is easy to ensure that the master site provides certain capabilities, and to accessthe default site layout. And with type-safe URLs, it's easy to link from the master site to subsites.

Hello World

Writing subsites is a little bit tricky, involving a number of different types. Let's start off with a simple Hello Worldsubsite:

-- START{-# LANGUAGE QuasiQuotes, TypeFamilies, MultiParamTypeClasses #-}{-# LANGUAGE TemplateHaskell, FlexibleInstances, OverloadedStrings #-}import Yesod

-- Subsites have foundations just like master sites.data HelloSub = HelloSub

-- We have a familiar analogue from mkYesod, with just one extra parameter.-- We'll discuss that later.mkYesodSub "HelloSub" [] [$parseRoutes|/ SubRootR GET|]

-- And we'll spell out the handler type signature.getSubRootR :: Yesod master => GHandler HelloSub master RepHtmlgetSubRootR = defaultLayout [$hamlet|Welcome to the subsite!|]

-- And let's create a master site that calls it.data Master = Master { getHelloSub :: HelloSub }

mkYesod "Master" [$parseRoutes|/ RootR GET/subsite SubsiteR HelloSub getHelloSub|]

instance Yesod Master where approot _ = ""

-- Spelling out type signature again.getRootR :: GHandler sub Master RepHtml -- could also replace sub with MastergetRootR = defaultLayout [$hamlet|<h1>Welcome to the homepage<p> Feel free to visit the # <a href=@{SubsiteR SubRootR}>subsite \ as well.|]

main = warpDebug 3000 $ Master HelloSub

This very simple example actually shows most of the complications involved in creating a subsite. Like a normalYesod application, everything in a subsite is centered around a foundation datatype, HelloSub in our case. We thenuse mkYesodSub, in much the same way that we use mkYesod, to create the route datatype and the dispatch/renderfunctions. (We'll come back to that extra parameter in a second.)

What's interesting is the type signature of getSubRootR. Up until now, we have tried to ignore the GHandlerdatatype, or if we need to acknowledge its existence, pretend like the first two type arguments are always the same.Now we get to finally acknowledge the truth about this funny datatype.

Page 66: Yesod Web Framework Book

66 | OpenTopic | Advanced

A handler function always has two foundation types associated with it: the subsite and the master site. When youwrite a normal application, those two datatypes are the same. However, when you are working in a subsite, theywill necessarily be different. So the type signature for getSubRootR uses HelloSub for the first argument andmaster for the second.

The defaultLayout function is part of the Yesod typeclass. Therefore, in order to call it, the master typeargument must be an instance of Yesod. The advantage of this approach is that any modifications to the master site'sdefaultLayout method will automatically be reflected in subsites.

When we embed a subsite in our master site route definition, we need to specify four pieces of information: the routeto use as the base of the subsite (in this case, /subsite), the constructor for the subsite routes (SubsiteR), thesubsite foundation data type (HelloSub) and a function that takes a master foundation value and returns a subsitefoundation value (getHelloSub).

In the definition of getRootR, we can see how the route constructor gets used. In a sense, SubsiteR promotes anysubsite route to a master site route, making it possible to safely link to it from any master site template.

Web Client CodeThis chapter is not yet written. It covers writing applications which access web services. We will cover:

• http-enumerator• gravatar• reCaptcha• Communicate with a Yesod service (JSON, cereal)

JSON Web Service

Let's create a very simple web service: it takes a JSON request and returns a JSON response. We're going to write theserver in WAI/Warp, and the client in http-enumerator. We'll be using aeson for JSON parsing and rendering.

Server

WAI uses the enumerator package to handle streaming request bodies, and efficiently generates responses usingblaze-builder. aeson uses attoparsec for parsing; by using attoparsec-enumerator we get easyinteroperability with WAI. And aeson can encode JSON directly into a Builder. This plays out as:

{-# LANGUAGE OverloadedStrings #-}import Network.Wai (Response (ResponseBuilder), Application)import Network.HTTP.Types (status200, status400)import Network.Wai.Handler.Warp (run)import Data.Aeson.Parser (json)import Data.Attoparsec.Enumerator (iterParser)import Control.Monad.IO.Class (liftIO)import Data.Aeson (Value (Object, String))import Data.Aeson.Encode (fromValue)import Data.Enumerator (catchError, Iteratee)import Control.Exception (SomeException)import Data.ByteString (ByteString)import qualified Data.Map as Mapimport Data.Text (pack)

main :: IO ()main = run 3000 app

app :: Applicationapp _ = flip catchError invalidJson $ do value <- iterParser json newValue <- liftIO $ modValue value return $ ResponseBuilder status200 [("Content-Type", "application/json")]

Page 67: Yesod Web Framework Book

OpenTopic | Advanced | 67

$ fromValue newValue

invalidJson :: SomeException -> Iteratee ByteString IO ResponseinvalidJson ex = return $ ResponseBuilder status400 [("Content-Type", "application/json")] $ fromValue $ Object $ Map.fromList [ ("message", String $ pack $ show ex) ]

-- Application-specific logic would go here.modValue :: Value -> IO ValuemodValue = return

Client

http-enumerator was written as a comapnion to WAI. It too uses enumerator and blaze-builder pervasively,meaning we once again get easy interop with aeson. A few extra comments for those not familiar with http-enumerator:

• A Manager is present to keep track of open connections, so that multiple requests to the same server use thesame connection. You usually want to use the withManager function to create and clean up this Manager, sinceit is exception safe.

• We need to know the size of our request body, which can't be determined directly from a Builder. Instead, weconvert the Builder into a lazy ByteString and take the size from there.

• There are a number of different functions for initiating a request. We use http, which allows us to directlyaccess the data stream. There are other higher level functions (such as httpLbs) that let you ignore the issues ofenumerators and get the entire body directly.

{-# LANGUAGE OverloadedStrings #-}import Network.HTTP.Enumerator ( http, parseUrl, withManager, RequestBody (RequestBodyLBS) , requestBody )import Data.Aeson (Value (Object, String))import qualified Data.Map as Mapimport Data.Aeson.Parser (json)import Data.Attoparsec.Enumerator (iterParser)import Control.Monad.IO.Class (liftIO)import Data.Enumerator (run_)import Data.Aeson.Encode (fromValue)import Blaze.ByteString.Builder (toLazyByteString)

main :: IO ()main = withManager $ \manager -> do value <- makeValue -- We need to know the size of the request body, so we convert to a -- ByteString let valueBS = toLazyByteString $ fromValue value req' <- parseUrl "http://localhost:3000/" let req = req' { requestBody = RequestBodyLBS valueBS } run_ $ flip (http req) manager $ \status headers -> do -- Might want to ensure we have a 200 status code and Content-Type is -- application/json. We skip that here. resValue <- iterParser json liftIO $ handleResponse resValue

-- Application-specific function to make the request valuemakeValue :: IO ValuemakeValue = return $ Object $ Map.fromList [ ("foo", String "bar") ]

-- Application-specific function to handle the response from the server

Page 68: Yesod Web Framework Book

68 | OpenTopic | Advanced

handleResponse :: Value -> IO ()handleResponse = print

Low Level TricksThis chapter, not yet written, will cover some more low-level details in Yesod. In particular, we'll touch on how to usethe enumerator interface in WAI to do memory efficient file and database streaming without resorting to lazy IO.

Page 69: Yesod Web Framework Book

OpenTopic | Appendices | 69

Appendices

Enumerator PackageOne of the upcoming patterns in Haskell is enumerators. They are designed to solve the problems of producing,modifying and consuming streams of data. Enumerators are considered a bit intimidating, possibly because:

• There are multiple implementations, all with slightly different approaches.• Some of the implementations (in my opinion) use incredibly confusing naming.• The tutorials that get written usually don't directly target an existing implementation, and work more on building

up intuition than giving instructions on how to use the library.

The Yesod framework uses enumerators behind the scenes in a number of places: in WAI/Warp for dealing withrequest and response bodies, in Persistent for receiving the results from a database query, and in various sidepackages like http-enumerator and xml-enumerator. We always use the enumerator package. Thatpackage is the topic of this chapter.

This chapter is composed of three main sections, each dealing with one of the main concepts:

• We start with iteratees, which are consumers. They are fed data and do something with it.• Next we cover enumerators which are producers. They feed data to an iteratee.• Finally, we address enumeratees. These are pipes which are fed data from an enumerator and in turn feed data to

an iteratee.

advanced:

You may be wondering why we bother with this, whenmost of these problems can be addressed by lazy I/O. Thebasic problem with lazy I/O is its non-determinism. Formore information, it's best to go to the original source onthis topic: Oleg.

Iteratees

Intuition

Let's say we want to write a function that sums the numbers in a list. Forgetting uninteresting details like space leaks,a perfectly good implementation could be:

sum1 :: [Int] -> Intsum1 [] = 0sum1 (x:xs) = x + sum1 xs

But let's say that we don't have a list of numbers. Instead, the user is typing numbers on the command line, and hitting"q" when done. In other words, we have a function like:

getNumber :: IO (Maybe Int)getNumber = do x <- readLine if x == "q" then return Nothing else return $ Just $ read x

We could write our new sum function as:

sum2 :: IO Intsum2 = do maybeNum <- getNumber case maybeNum of Nothing -> return 0

Page 70: Yesod Web Framework Book

70 | OpenTopic | Appendices

Just num -> do rest <- sum2 return $ num + rest

It's fairly annoying to have to write two completely separate sum functions just because our data source changed.Ideally, we would like to generalize things a bit. Let's start by noticing a similarity between these two functions: theyboth only yield a value when they are informed that there are no more numbers. In the case of sum1, we check for anempty list; in sum2, we check for Nothing.

Stream Datatype

The first datatype defined in the enumerator package is:

data Stream a = Chunks [a] | EOF

The EOF constructor indicates that no more data is available. The Chunks constructor simply allows us to putmultiple pieces of data together for efficiency. We could now rewrite sum2 to use this Stream datatype:

getNumber2 :: IO (Stream Int)getNumber2 = do maybeNum <- getNumber -- using the original getNumber function case maybeNum of Nothing -> return EOF Just num -> return $ Chunks [num]

sum3 :: IO Intsum3 = do stream <- getNumber2 case stream of EOF -> return 0 Chunks nums -> do let nums' = sum nums rest <- sum3 return $ nums' + rest

Not that it's much better than sum2, but at least it shows how to use the Stream datatype. The problem here is that westill refer explicitly to the getNumber2 function, hard-coding the data source.

One possible solution is to make the data source an argument to the sum function, ie:

sum4 :: IO (Stream Int) -> IO Intsum4 getNum = do stream <- getNum case stream of EOF -> return 0 Chunks nums -> do let nums' = sum nums rest <- sum4 getNum return $ nums' + rest

That's all well and good, but let's pretend we want to have two datasources to sum over: values the user enters on thecommand line, and some numbers we read over an HTTP connection, perhaps. The problem here is one of control:sum4 is running the show here by calling getNum. This is a pull data model. Enumerators have an inversion ofcontrol/push model, putting the enumerator in charge. This allows cool things like feeding in multiple data sources,and also makes it easier to write enumerators that properly deal with resource allocation.

The Step datatype

So we need a new datatype that will represent the state of our summing operation. We're going to allow ouroperations to be in one of three states:

• Waiting for more data.• Already calculated a result.• For convenience, we also have an error state. This isn't strictly necessary (it could be modeled by choosing an

EitherT kind of monad, for example), but it's simpler.

Page 71: Yesod Web Framework Book

OpenTopic | Appendices | 71

As you could guess, these states will correspond to three constructors for the Step datatype. The error state is modeledby Error SomeException, building on top of Haskell's extensible exception system. The already calculatedconstructor is:

Yield b (Stream a)

Here, a is the input to our iteratee and b is the output. This constructor allows us to simultaneously produce a resultand save any "leftover" input for another iteratee that may run after us. (This won't be the case with the sum function,which always consumes all its input, but we'll see some other examples that do not consume all output.)

Now the question is how to represent the state of an iteratee that's waiting for more data. You might at first want todeclare some datatype to represent the internal state and pass that around somehow. That's not how it works: instead,we simply use a function (very Haskell of us, right?):

Continue (Stream a -> Iteratee a m b)

Eureka! We've finally seen the Iteratee datatype! Actually, Iteratee is a very boring datatype that is only present toallow us to declare cool instances (eg, Monad) for our functions. Iteratee is defined as:

newtype Iteratee a m b = Iteratee (m (Step a m b))

And the complete Step datatype is:

data Step a m b = Error SomeException | Yield b (Stream a) | Continue (Stream a -> Iteratee a m b)

This is important: Iteratee is just a newtype wrapper around a Step inside a monad. Just keep that in mind as youlook at definitions in the enumerator package. So knowing this, we can think of the Continue constructor as:

Continue (Stream a -> m (Step a m b))

That's much easier to approach: that function takes some input data and returns a new state of the iteratee. Let's seewhat our sum function would look like using this Step datatype:

sum5 :: Monad m => Step Int m Int -- Int input, any monad, Int outputsum5 = Continue $ go 0 -- a common pattern, you always start with a Continue where go :: Monad m => Int -> Stream Int -> Iteratee Int m Int -- Add the new input to the running sum and create a new Continue go runningSum (Chunks nums) = do let runningSum' = runningSum + sum nums -- This next line is *ugly*, good thing there are some helper -- functions to clean it up. More on that below. Iteratee $ return $ Continue $ go runningSum' -- Produce the final result go runningSum EOF = Iteratee $ return $ Yield runningSum EOF

Note: In order to run this code, you can use run_ $enumList 8 [1..10] sum5. But this gets into someof the Enumerator black magic we won't discuss till later.

The first real line (Continue $ go 0) initializes our iteratee to its starting state. Just like every other sumfunction, we need to explicitly state that we are starting from 0 somewhere. The real workhorse is the go function.Notice how we are really passing the state of the iteratee around as the first argument to go: this is also a verycommon pattern in iteratees.

We need to handle two different cases: when handed an EOF, the go function must Yield a value. (Well, it couldalso produce an Error value, but it definitely cannot Continue.) In that case, we simply yield the running sum and saythere was no data left over. When we receive some input data via Chunks, we simply add it to the running sum andcreate a new Continue based on the same go function.

Page 72: Yesod Web Framework Book

72 | OpenTopic | Appendices

Now let's work on making that function a little bit prettier by using some built-in helper functions. The patternIteratee . return is common enough to warrant a helper function, namely:

returnI :: Monad m => Step a m b -> Iteratee a m breturnI = Iteratee . return

So for example,

go runningSum EOF = Iteratee $ return $ Yield runningSum EOF

becomes

go runningSum EOF = returnI $ Yield runningSum EOF

But even that is common enough to warrant a helper function:

yield :: Monad m => b -> Stream a -> Iteratee a m byield x chunk = returnI $ Yield x chunk

so our line becomes

go runningSum EOF = yield runningSum EOF

Similarly,

Iteratee $ return $ Continue $ go runningSum'

becomes

continue $ go runningSum'

Monad instance for Iteratee

This is all very nice: we now have an iteratee that can be fed numbers from any monad and sum them. It can eventake input from different sources and sum them together. (By the way, I haven't actually shown you how to feed thosenumbers in: that is in part 2 about enumerators.) But let's be honest: sum5 is an ugly function. Isn't there somethingeasier?

In fact, there is. Remember how I said Iteratee really just existed to facilitate typeclass instances? This includes amonad instance. Feel free to look at the code to see how that instance is defined, but here we'll just look at how to useit:

sum6 :: Monad m => Iteratee Int m Intsum6 = do maybeNum <- head -- not head from Prelude! case maybeNum of Nothing -> return 0 Just i -> do rest <- sum6 return $ i + rest

That head function is not from Prelude, it's from the Data.Enumerator module. Its type signature is:

head :: Monad m => Iteratee a m (Maybe a)

which basically means give me the next piece of input if it's there. We'll look at this function in more depth in a bit.

Go compare the code for sum6 with sum2: they are amazingly similar. You can often build up more complicatediteratees by using some simple iteratees and the Monad instance of Iteratee.

Interleaved I/O

Alright, let's look at a totally different problem. We want to be fed some strings and print them to the screen one lineat a time. One approach would be to use lazy I/O:

lazyIO :: IO ()lazyIO = do s <- lines `fmap` getContents mapM_ putStrLn s

Page 73: Yesod Web Framework Book

OpenTopic | Appendices | 73

But this has two drawbacks:• It's tied down to a single input source, stdin. This could be worked around with an argument giving a datasource.• But let's say the data source is some scarce resource (think: file handles on a very busy web server). We have no

guarantees with lazy I/O of when those file handles will be released.Let's look at how to write this in our new high-level monadic iteratee approach:

interleaved :: MonadIO m => Iteratee String m ()interleaved = do maybeLine <- head case maybeLine of Nothing -> return () Just line -> do liftIO $ putStrLn line interleaved

The liftIO function comes from the transformers package, and simply promotes an action in the IO monad to anyarbitrary MonadIO action. Notice how we don't really track any state with this iteratee: we don't care about its result,only its side effects.

Implementing head

As a last example, let's actually implement the head function.

head' :: Monad m => Iteratee a m (Maybe a)head' = continue go where go (Chunks []) = continue go go (Chunks (x:xs)) = yield (Just x) (Chunks xs) go EOF = yield Nothing EOF

Like our sum6 function, this also wraps an inner "go" function with a continue. However, we now have three clausesfor our go function. The first handles the case of Chunks []. To quote the enumerator docs:

(Chunks []) is used to indicate that a stream is still active, but currently has no available data. Iteratees should ignoreempty chunks.

The second clause handles the case where we are given some data. In this case, we yield the first element in the list,and return the rest as leftover data. The third clause handles the end of input by returning Nothing.

Exercises• Rewrite sum6 using liftFoldL'.• Implement the consume function using first the high-level functions like head, and then using only low-level stuff.• Write a modified version of consume that only keeps every other value, once again using high-level functions and

then low-level constructors.

Summary

Here's what I consider the most important things to glean from this tutorial:• Iteratee is a simple wrapper around the Step datatype to allow for cool typeclass instances.• Using the Monad instance of Iteratee can allow you to build up complicated iteratees from simpler ones.• The three states an enumerator can be in are Continue (still processing data), Yield (a result is ready) and Error

(duh).• Well behaved iteratees will never return a Continue after receiving an EOF.

Enumerators

Extracting a value

So far, we've written a few iteratees, but we still don't know how to extract values from them. To start, let's rememberthat Iteratee is just a newtype wrapper around Step:

newtype Iteratee a m b = Iteratee { runIteratee :: m (Step a m b) }

Page 74: Yesod Web Framework Book

74 | OpenTopic | Appendices

First we need to unwrap the Iteratee and deal with the Step value inside. Remember also that Step has threeconstructors: Continue, Yield and Error. We'll handle the Error constructor by returning our result in an Either. Yieldalready provides the data we're looking for.

The tricky case is Continue: here, we have an iteratee that is still expecting more data. This is where the EOFconstructor comes in handy: it's our little way to tell the iteratee to finish what it's doing and get on with things. If youremember from above, I said a well-behaving iteratee will never return a Continue after receiving an EOF; now we'llsee why:

extract :: Monad m => Iteratee a m b -> m (Either SomeException b)extract (Iteratee mstep) = do step <- mstep case step of Continue k -> do let Iteratee mstep' = k EOF step' <- mstep' case step' of Continue _ -> error "Misbehaving iteratee" Yield b _ -> return $ Right b Error e -> return $ Left e Yield b _ -> return $ Right b Error e -> return $ Left e

Fortunately, you don't need to redefine this yourself: enumerator includes both a run and run_ function. Let's goahead and use it on our sum6 function:

main = run_ sum6 >>= print

If you run this, the result will be 0. This emphasizes an important point: an iteratee is not just how to processincoming data, it is the state of the processing. In this case, we haven't done anything to change the initial state ofsum6, so we still have the initial value of 0.

To give an analogy: think of an iteratee as a machine. When you feed it data, you modify the internal state but youcan't see any of those changes on the outside. When you are done feeding the data, you press a button and it spits outthe result. If you don't feed in any data, your result is the initial state.

Adding data

Let's say that we actually want to sum some numbers. For example, the numbers 1 to 10. We need some way to feedthat into our sum6 iteratee. In order to approach this, we'll once again need to unwrap our Iteratee and deal with theStep value directly.

In our case, we know with certainty that the Step constructor we used is Continue, so it's safe to write our function as:

sum7 :: Monad m => Iteratee Int m Intsum7 = Iteratee $ do Continue k <- runIteratee sum6 runIteratee $ k $ Chunks [1..10]

But in general, we won't know what constructor will be lying in wait for us. We need to properly deal with Continue,Yield and Error. We've seen what to do with Continue: feed it the data. With Yield and Error, the right action ingeneral is to do nothing, since we've already arrived at our final result (either a successful Yield or an Error). So the"proper" way to write the above function is:

sum8 :: Monad m => Iteratee Int m Intsum8 = Iteratee $ do step <- runIteratee sum6 case step of Continue k -> runIteratee $ k $ Chunks [1..10] _ -> return step

Page 75: Yesod Web Framework Book

OpenTopic | Appendices | 75

Enumerator type synonym

What we've done with sum7 and sum8 is perform a transformation on the Iteratee. But we've done this in a verylimited way: we've hard-coded in the original Iteratee function (sum6). We could just make this an argument to thefunction:

sum9 :: Monad m => Iteratee Int m Int -> Iteratee Int m Intsum9 orig = Iteratee $ do step <- runIteratee orig case step of Continue k -> runIteratee $ k $ Chunks [1..10] _ -> return step

But since we always just want to unwrap the Iteratee value anyway, it turns out that it's more natural to make theargument of type Step, ie:

sum10 :: Monad m => Step Int m Int -> Iteratee Int m Intsum10 (Continue k) = k $ Chunks [1..10]sum10 step = returnI step

This type signature (take a Step, return an Iteratee) turns out to be very common:

type Enumerator a m b = Step a m b -> Iteratee a m b

Meaning sum10's type signature could also be expressed as:

sum10 :: Monad m => Enumerator Int m Int

Of course, we need some helper function to connect an Enumerator and an Iteratee:

applyEnum :: Monad m => Enumerator a m b -> Iteratee a m b -> Iteratee a m bapplyEnum enum iter = Iteratee $ do step <- runIteratee iter runIteratee $ enum step

Let me repeat the intuition here: the Enumerator is transforming the Iteratee from its initial state to a new state byfeeding it more data. In order to use this function, we could write:

run_ (applyEnum sum10 sum6) >>= print

This results in 55, exactly as we'd expect. But now we can see one of the benefits of enumerators: we can use multipledata sources. Let's say we have another enumerator:

sum11 :: Monad m => Enumerator Int m Intsum11 (Continue k) = k $ Chunks [11..20]sum11 step = returnI step

Then we could simply apply both enumerators:

run_ (applyEnum sum11 $ applyEnum sum10 sum6) >>= print

And we would get the result 210. (Yes, (1 + 20) * 10 = 210.) But don't worry, you don't need to write this applyEnumfunction yourself: enumerator provides a $$ operator which does the same thing. Its type signature is a bit scarier,since it's a generalization of applyEnum, but it works the same, and even makes code more readable:

run_ (sum11 $$ sum10 $$ sum6) >>= print

$$ is a synonym for ==<<, which is simply flip >>==. I find $$ the most readable, but YMMV (YMMV).

Some built-in enumerators

Of course, writing a whole function just to pass some numbers to our sum function seems a bit tedious. We couldeasily make the list an argument to the function:

sum12 :: Monad m => [Int] -> Enumerator Int m Intsum12 nums (Continue k) = k $ Chunks numssum12 _ step = returnI step

Page 76: Yesod Web Framework Book

76 | OpenTopic | Appendices

But now there's not even anything Int-specific in our function. We could easily generalize this to:

genericSum12 :: Monad m => [a] -> Enumerator a m bgenericSum12 nums (Continue k) = k $ Chunks numsgenericSum12 _ step = returnI step

And in fact, enumerator comes built in with the enumList function which does this. enumList also takes an Integerargument to indicate the maximum number of elements to stick in a chunk. For example, we could write:

run_ (enumList 5 [1..30] $$ sum6) >>= print

(That produces 465 if you're counting.) The first argument to enumList should never affect the result, though it mayhave some performance impact.

Data.Enumerator includes two other enumerators: enumEOF simply passes an EOF to the iteratee. concatEnums isslightly more interesting; it combines multiple enumerators together. For example:

run_ (concatEnums [ enumList 1 [1..10] , enumList 1 [11..20] , enumList 1 [21..30] ] $$ sum6) >>= print

This also produces 465.

Some non-pure input

Enumerators are much more interesting when they aren't simply dealing with pure values. In the first part of thistutorial, we gave the example of the user entering numbers on the command line:

getNumber :: IO (Maybe Int)getNumber = do x <- getLine if x == "q" then return Nothing else return $ Just $ read x

sum2 :: IO Intsum2 = do maybeNum <- getNumber case maybeNum of Nothing -> return 0 Just num -> do rest <- sum2 return $ num + rest

We referred to this as the pull-model: sum2 pulled each value from getNumber. Let's see if we can rewrite getNumberto be a pusher instead of a pullee.

getNumberEnum :: MonadIO m => Enumerator Int m bgetNumberEnum (Continue k) = do x <- liftIO getLine if x == "q" then continue k else k (Chunks [read x]) >>== getNumberEnumgetNumberEnum step = returnI step

First, notice that we check which constructor was passed, and only perform any actions if it was Continue. If it wasContinue, we get the line of input from the user. If the line is "q" (our indication to stop feeding in values), we donothing. You might have thought that we should pass an EOF. But if we did that, we'd be preventing other data frombeing sent to this iteratee. Instead, we simply return the original Step value.

Page 77: Yesod Web Framework Book

OpenTopic | Appendices | 77

If the line was not "q", we convert it to an Int via read, create a Stream value with the Chunks datatype, and pass it tok. (If we wanted to do things properly, we'd check if x is really an Int and use the Error constructor; I leave that as anexercise to the reader.) At this point, let's look at type signatures:

k (Chunks [read x]) :: Iteratee Int m b

If we simply left off the rest of the line, our program would typecheck. However, it would only ever read one valuefrom the command line; the >>== getNumberEnum causes our enumerator to loop.

One last thing to note about our function: notice the b in our type signature.

getNumberEnum :: MonadIO m => Enumerator Int m b

This is saying that our Enumerator can feed Ints to any Iteratee accepting Ints, and it doesn't matter what thefinal output type will be. This is in general the way enumerators work. This allows us to create drastically differentiteratees that work with the same enumerators:

intsToStrings :: (Show a, Monad m) => Iteratee a m StringintsToStrings = (unlines . map show) `fmap` consume

And then both of these lines work:

run_ (getNumberEnum $$ sum6) >>= printrun_ (getNumberEnum $$ intsToStrings) >>= print

Exercises1. Write an enumerator that reads lines from stdin (as Strings). Make sure it works with this iteratee:

printStrings :: Iteratee String IO () printStrings = do mstring <- head casemstring of Nothing -> return () Just string -> do liftIO $ putStrLn stringprintStrings

2. Write an enumerator that does the same as above with words (ie, delimit on any whitespace). It should work withthe same Iteratee as above.

3. Do proper error handling in the getNumberEnum function above when the string is not a proper integer.4. Modify getNumberEnum to pull its input from a file instead of stdin.5. Use your modified getNumberEnum to sum up the values in two different files.

Summary• An enumerator is a step transformer: it feeds data into an iteratee to produce a new iteratee with an updated state.• Multiple enumerators can be fed into a single iteratee, and we finally use the run and run_ functions to extract

results.• We can use the $$, >>== and ==<< operators to apply an enumerator to an iteratee.• When writing an enumerator, we only feed data to an iteratee in the Continue state; Yield and Error already

represent final values.

Enumeratees

Generalizing getNumberEnum

Earlier, we created a getNumberEnum function with a type signature:

getNumberEnum :: MonadIO m => Enumerator Int m b

If you don't remember, this means getNumberEnum produces a stream of Ints. In particular, our getNumberEnumfunction read lines from stdin, converted them to ints and fed them into an iteratee. It stopped reading lines when itsaw a "q".

But this functionality seems like it could be useful outside the realm of Ints. We may like to deal with the originalStrings, for example, or Bools, or a bunch of other things. We could easily define a more generalized function whichsimply doesn't do the String to Int conversion:

lineEnum :: MonadIO m => Enumerator String m blineEnum (Continue k) = do x <- liftIO getLine

Page 78: Yesod Web Framework Book

78 | OpenTopic | Appendices

if x == "q" then continue k else k (Chunks [x]) >>== lineEnumlineEnum step = returnI step

Cool, let's plug this into our sumIter function (I've renamed the sum6 function from the previous two parts):

lineEnum $$ sumIter

Actually, that doesn't type check: lineEnum produces Strings, and sumIter takes Ints. We need to modify one ofthem somehow.

sumIterString :: Monad m => Iteratee String m IntsumIterString = Iteratee $ do innerStep <- runIteratee sumIter return $ go innerStep where go :: Monad m => Step Int m Int -> Step String m Int go (Yield res _) = Yield res EOF go (Error err) = Error err go (Continue k) = Continue $ \strings -> Iteratee $ do let ints = fmap read strings :: Stream Int step <- runIteratee $ k ints return $ go step

What we've done here is wrap around the original iteratee. As usual, we first need to unwrap the Iteratee constructorand the monad to get at the heart of the Step value. Once we have that innerStep value, we pass it to the go function,which simply transforms that values in the Stream value from Strings to Ints.

Even more general

Of course, it would be nice if we could apply this transformation to *any* iteratee. To start with, let's just pass theinner iteratee and the mapping function as parameters.

mapIter :: Monad m => (aOut -> aIn) -> Iteratee aIn m b -> Iteratee aOut m bmapIter f innerIter = Iteratee $ do innerStep <- runIteratee innerIter return $ go innerStep where go (Yield res _) = Yield res EOF go (Error err) = Error err go (Continue k) = Continue $ \strings -> Iteratee $ do let ints = fmap f strings step <- runIteratee $ k ints return $ go step

We could call this like:

run_ (lineEnum $$ mapIter read sumIter) >>= print

Nothing much to see here, it's basically identical to the previous version. What's funny is that enumerator comes builtin with a map function to do just this, but it has a significantly different type signature:

map :: Monad m => (ao -> ai) -> Enumeratee ao ai m b

since:

type Enumeratee aOut aIn m b = Step aIn m b -> Iteratee aOut m (Step aIn m b)

that's equivalent to:

map :: Monad m => (aOut -> aIn) -> Step aIn m b -> Iteratee aOut m (Step aIn m b)

What's with all this extra complication in type signature? Well, it's not necessary for map itself, but it is necessaryfor a whole bunch of other similar functions. But let's focus on this map for a second so we don't get lost: the firstargument is the same old mapping function we had before. The second argument is a Step value. This isn't really so

Page 79: Yesod Web Framework Book

OpenTopic | Appendices | 79

surprising: in our mapIter, we took an Iteratee with the same parameters, and we internally just unwrapped it to aStep.

But what's happening with that return value? Remembering the meanings for all these datatypes, it's an Iteratee whichwill be fed a stream of aOuts and return a Step (aka, a new iteratee, right?). This kind of makes intuitive sense: we'veintroduced a middle man which accepts input from one source and transforms a Step to a newer state.

But now perhaps the trickiest part of the whole thing: how do we actually use this map function? It turns out that anEnumeratee is close enough in type signature to an Enumerator that we can just do:

map read $$ sumIter

But the type signature on that turns out to be a little bit weird:

Iteratee String m (Step Int m Int)

Remembering that an Iteratee is just a wrapped up Step, what we've got here is an iteratee that takes Strings andreturns an Iteratee, which in turn takes Ints and produces an Int. Having this fancy result allows us to do one of ourgreat tricks with iteratees: plug in data from multiple sources. For example, we could plug some Strings into thiswhole ugly thing, run it, get a new iteratee which takes Ints, feed that some Ints and get an Int result.

(If all that went over your head, don't worry. I won't be talking about that kind of stuff any more.)

But often times, we don't need all of that power. We just want to stick our enumeratee onto our iteratee and get anew iteratee. In our case, we want to attach our map onto the sumIter to produce a new iteratee that takes Strings andreturns Ints. In order to do that, we need a function like this:

unnest :: Monad m => Iteratee String m (Step Int m Int) -> Iteratee String m Intunnest outer = do -- using the Monad instance of Iteratee inner <- outer -- inner :: Step Int m Int go inner where go (Error e) = throwError e go (Yield x _) = yield x EOF go (Continue k) = k EOF >>== go

We can then run our unholy mess with:

run_ (lineEnum $$ unnest $ map read $$ sumIter) >>= print

And actually, the unnest function is available in Data.Enumerator, and it's called joinI. So we should really write:

run_ (lineEnum $$ joinI $ map read $$ sumIter) >>= print

Skipping

Let's write a slightly more interesting enumeratee: this one skips every other input value.

skip :: Monad m => Enumeratee a a m bskip (Continue k) = do x <- head _ <- head -- the one we're skipping case x of Nothing -> return $ Continue k Just y -> do newStep <- lift $ runIteratee $ k $ Chunks [y] skip newStepskip step = return step

What's interesting about the approach here is how similar it looks to an Enumerator. We're doing a lot of the samethings: checking if the Step value is a Continue; if it's not, then simply return it. Then we capitalize on the IterateeMonad instance, using the head function to pop two values out of the stream. If there's no more data, we return theoriginal Continue value: just like with an Enumerator, we don't give an EOF so that we can feed more data into theiteratee later. If there is data, we pass it off to the iteratee, get our new step value and then loop.

Page 80: Yesod Web Framework Book

80 | OpenTopic | Appendices

And what's cool about enumeratees is we can chain these all together:

run_ (lineEnum $$ joinI $ skip $$ joinI $ map read $$ sumIter) >>= print

Here, we read lines, skip every other input, convert the Strings to Ints and sum them.

Real life examples: http-enumerator package

I started working on these tutorials as I was working on the http-enumerator package. I think the usage ofenumeratees there is a great explanation of the benefits they can offer in real life. There are three different ways theresponse body can be broken up:

• Chunked encoding. In this case, the web server gives a hex string specifying the length of the next chunk and thenthat chunk. At the end, it sends a 0 to indicate the end of that response.

• Content length. Here, the web server sends a header before any of the body is sent specifying the total length ofthe body.

• Nothing at all. In this case, the response body lasts until an end-of-file.

In addition, the body may or may not be GZIP compressed. We end up with the following enumeratees, each withtype signature Enumeratee ByteString ByteString m b: chunkedEncoding, contentLength and ungzip.We then get to do something akin to:

let parseBody x = if ("transfer-encoding", "chunked") `elem` responseHeaders then joinI $ chunkedEncoding $$ x else case mlen of Just len -> joinI $ contentLength len $$ x Nothing -> x -- no enumeratee applied at alllet decompress x = if ("content-encoding", "gzip") `elem` responseHeaders then joinI $ ungzip $$ x else xrun_ $ socketEnumerator $$ parseBody $ decompress $ bodyIteratee

We create a chain: the data from the server is fed into the parseBody function. In the case of chunked encoding, thedata is processed appropriately and then headers are filtered out. If we are dealing with content length, then only thespecified number of bytes are read. And in the case of neither of those, parseBody is a no-op.

Whatever the case may be, the raw response body is then fed into decompress. If the body is GZIPed, then ungzipinflates it, otherwise decompress is a no-op. Finally, the parsed and inflated data is fed into the user-suppliedbodyIteratee function. The user remains blissfully unaware of any steps the data took to get to him/her.

Exercises1. Write an enumeratee which takes hex chars (eg, "DEADBEEF") to Word8s. Its type signature should be

Enumeratee Char Word8 m b.2. Write the opposite enumeratee, eg Enumeratee Word8 Char m b.3. Create a quickcheck property that ensures that these two functions work correctly.

Summary• Enumeratees are the pipes connecting enumerators to iteratees.• The strange type signature of an Enumeratee hides a lot of possible power. Especially notice how similar their

type signatures are to Enumerators.• You can merge an Enumeratee into an Iteratee with joinI $ enumeratee $$ iteratee.• Don't forget that you can use the Monad instance of Iteratee when creating your own enumeratees.• You can always compose multiple enumeratees together, such as in http-enumerator.

Web Application InterfaceIt is a problem almost every language used for web development has dealt with: the low level interface betweenthe web server and the application. The earliest example of a solution is the venerable and battle-worn CGI (CGI),providing a language-agnostic interface using only standard input, standard output and environment variables.

Page 81: Yesod Web Framework Book

OpenTopic | Appendices | 81

Back when Perl was becoming the de facto web programming language, a major shortcoming of CGI becameapparent: the process needed to be started anew for each request. When dealing with an interpretted language andapplication requiring database connection, this overhead became unbearable. FastCGI (and later SCGI) arose as asuccessor to CGI, but it seems that much of the programming world went in a different direction.

Each language began creating its own standard for interfacing with servers. mod_perl. mod_python. mod_php.mod_ruby. Within the same language, multiple interfaces arose. In some cases, we even had interfaces on top ofinterfaces. And all of this led to much duplicated effort: a Python application designed to work with FastCGI wouldn'twork with mod_python; mod_python only exists for certain webservers; and this programming language specificextensions need to be written for each programming language.

Haskell has its own history. We originally had the cgi package, which provided a monadic interface. The fastcgipackage then provided the same interface. Meanwhile, it seemed that the majority of Haskell web developmentfocused on the standalone server. The problem is that each server comes with its own interface, meaning that youneed to target a specific backend. This means that it is impossible to share common features, like GZIP encoding,development servers, and testing frameworks.

WAI attempts to solve this, by providing a generic and efficient interface between web servers and applications. Anyhandler supporting the interface can serve any WAI application, while any application using the interface can run onany handler.

At the time of writing, there are various backends, including Warp, FastCGI, and development server. wai-extraprovides many common middlewares like GZIP, JSON-P and virtual hosting. wai-test makes it easy to write unittests, and wai-handler-devel lets you develop your applications without worrying about stopping to compile.Yesod targets WAI, and Happstack is in the process of converting over as well. It's also used by some applicationsthat skip the framework entirely, including the new Hoogle.

The Interface

The interface itself is very straight-forward: an application takes a request and returns a response. A response is anHTTP status, a list of headers and a response body. A request contains various information: the requested path, querystring, request body, HTTP version, and so on.

Response Body

Haskell has a datatype known as a lazy bytestring. By utilizing laziness, you can create large values withoutexhausting memory. Using lazy I/O, you can do such tricks as having a value which represents the entire contents of afile, yet only occupies a small memory footprint. In theory, a lazy bytestring is the only representation necessary for aresponse body.

In practice, while lazy byte strings are wonderful for generating "pure" values, the lazy I/O necessary to read a fileintroduces some non-determinism into our programs. When serving thousands of small files a second, the limitingfactor is not memory, but file handles. Using lazy I/O, file handles may not be freed immediately, leading to resourceexhaustion. To deal with this, WAI uses enumerators.

Enumerators are really a simple concept with a lot of complications surrounding them. Most basically, an enumeratoris a data producer, that hands chunks of data one at a time to an iteratee, which is a data consumer. In the case ofWAI, the request body would be an enumerator which would produce data by reading it from a file. The iterateewould be the server, which would send these chunks of data to the client.

There are two further optimizations: many systems provide a sendfile system call, which sends a file directly to asocket, bypassing a lot of the memory copying inherent in more general I/O system calls. Additionally, there is adatatype in Haskell called Builder which allows efficient copying of bytes into buffers.

The WAI response body therefore has three constructors: one for pure builders (ResponseBuilder), one forenumerators of builders (ResponseEnum) and one for files (ResponseFile).

Page 82: Yesod Web Framework Book

82 | OpenTopic | Appendices

Request Body

In order to avoid the need to load the entire request body into memory, we use enumerators here as well. Since thepurpose of these values are for reading (not writing), we use ByteStrings in place of Builders. This is allcontained in the type signature of an Application:

type Application = Request -> Iteratee ByteString IO Response

This states that an application is a function, which takes a Request value and returns an action. This actionconsumes a stream of ByteStrings (the request body) and produces a Response.

The request body could in theory contain any type of data, but the most common are URL encoded and multipartform data. The wai-extra package contains built-in support for parsing these in a memory-efficient manner.

Hello World

To demonstrate the simplicity of WAI, let's look at a hello world example. In this example, we're going to use theOverloadedStrings language extension to avoid explicitly packing string values into bytestrings.

1 {-# LANGUAGE OverloadedStrings #-}2 import Network.Wai3 import Network.HTTP.Types (statusOK)4 import Network.Wai.Handler.Warp (run)56 application _ = return $7 responseLBS statusOK [("Content-Type", "text/plain")] "Hello World"89 main = run 3000 application

Lines 2 through 4 perform our imports. Warp is provided by the warp package, and is the premiere WAI backend.WAI is also built on top of the http-types package, which provides a number of datatypes and conveniencevalues, including statusOK.

First we define our application. Since we don't care about the specific request parameters, we ignore the argument tothe function. For any request, we are returning a response with status code 200 ("OK"), and text/plain content typeand a body containing the words "Hello World". Pretty straight-forward.

Middleware

In addition to allowing our applications to run on multiple backends without code changes, the WAI allows us anotherbenefits: middleware. Middleware is essentially an application transformer, taking one application and returninganother one.

Middlewares can be used to provide lots of services: cleaning up URLs, authentication, caching, JSON-P requests.But perhaps the most useful and most intuitive middleware is gzip compression. The middleware works very simply:it parses the request headers to determine if a client supports compression, and if so compresses the response bodyand adds the appropriate response header.

The great thing about middlewares is that they are unobtrusive. Let's see how we would apply the gzip middleware toour hello world application.

1 {-# LANGUAGE OverloadedStrings #-}2 import Network.Wai3 import Network.Wai.Handler.Warp (run)4 import Network.Wai.Middleware.Gzip (gzip)5 import Network.HTTP.Types (statusOK)67 application _ = return $ responseLBS statusOK [("Content-Type", "text/plain")]8 "Hello World"910 main = run 3000 $ gzip application

Page 83: Yesod Web Framework Book

OpenTopic | Appendices | 83

We added an import line to actually have access to the middleware, and then simply applied gzip to ourapplication. You can also chain together multiple middlewares: a line such as gzip False $ jsonp $othermiddleware $ myapplication is perfectly valid. One word of warning: the order the middleware isapplied can be important. For example, jsonp needs to work on uncompressed data, so if you apply it after you applygzip, you'll have trouble.

Blog posts that should be chaptersFollowing is a list of blog posts that should ultimately have their content merged in with the rest of the book.Unfortunately, I just haven't had time to do so yet.

• The Magic of Yesod, part 2 December 25, 2010 Continues the previous blog post, covering the static subsite,parseRoutes and mkYesod

• The Magic of Yesod, part 1 December 23, 2010 Explains the basics of Template Haskell and Quasi-Quotation, andhow it ties in with Hamlet, Cassius and Julius

• Custom Forms and the Form Monad October 20, 2010 Explains a new way of creating forms introduced in Yesod0.6.

• Ajax Push July 22, 2010 An extension of the basic chat example to use Ajax push.• The Handler Monad July 15, 2010 Slightly out-of-date, as we've migrated back to a form of either monad

transformer.• Fringe benefits of type-safe URLs June 24, 2010 Covers breadcrumbs, dedicated static file servers and

authorization.• RESTful Content June 18, 2010

Frequently Asked QuestionsPerhaps it's not common to put a FAQ in a book, but it seems that there are a number of questions that come upregularly enough. Hopefully this will help.

If you can think of any questions you would like added, please write a comment to this page.

Why does the scaffolded site use cassiusFileDebug and juliusFileDebug, but does notuse hamletFileDebug?

For efficiency and type safety, the template lanugages used in Yesod are compile-time interpreted. This is great forproduction, but can slow down development. As a workaround, this languages provide a "debug" version of theirrun functions which does some initial compile-time parsing to determine which variables to use, and then reads thetemplate at runtime to pick up any changes.

The scaffolded site specifically avoids hamletFileDebug for two reasons

• It does not handle Hamlet's polymorphism. A Hamlet template can be converted to a type of Html, Hamlet orWidget, while hamletFileDebug only works with Hamlet. In theory, this could be fixed.

• hamletFileDebug does not work at all when you add new variables to the template. This is a problem that cannotbe fixed.

The first issue does not affect Cassius and Julius since they do not have any polymorphism. The second one is notas big a deal for Cassius and Julius: it's simply not as common to interpolate variables in your CSS and Javascript. Ipersonally found that hamletFileDebug only worked for me half the time.

Instead, a more robust solution is to use wai-handler-devel (also included in the scaffolded site). This willautomatically reinterpret your app after a change to your Haskell or Hamlet template files. This is usually very fastfor small sites, but gets slow for large sites. Hopefully in the near future, wai-handler-devel will switch from using thehint package to the plugins package, which will help alleviate that burden.

Page 84: Yesod Web Framework Book

84 | OpenTopic | Appendices

Migration Guide: 0.6 to 0.7The following is a migration guide from Yesod 0.6.* to Yesod 0.7.0. These are notes I took while performing themigration on the Haskellers.com website. It may not be completely exhaustive, but it should be a good start.

If you run into issues which are not addressed here, please add a comment on this paragraph so that I can update thearticle and others can see what's missing.

No Warnings

Before you get started, I recommend you get your current code to compile without any warnings. That way, as youbegin migrating, you'll know that any new warnings are caused by the migration process itself. In fact, you may evenwant to turn on -Wall -Werror in your cabal file.

Install yesod

In theory, this will simply require running cabal update && cabal install yesod. In practice, cabalmay not be able to figure everything out for you. If you run into DLL hell, the simplest solution currently is to justdelete your .ghc folder. Please note that you need to have the happy and alex packages installed to install yesod.

Update cabal file

Almost all underlying packages for Yesod have been upgraded, including WAI, Hamlet, etc. Make sure to includenewer versions. For the most part, if you depend on the yesod package itself, you can leave off version bounds onunderlying packages. However, cabal may not always do the right thing.

Some specific notes:• Obviously, make sure your version bounds on the yesod package allow 0.7.0. I recommend >= 0.7 && < 0.8• wai-extra no longer provides SimpleServer. Instead, you should use Warp.• Make sure to add a -threaded GHC option when compiling all executables, even devel-server.Make sure to run cabal install to automatically install any dependencies. If you run into DLL hell here, then(surprise) an easy solution is to wipe out .ghc and run cabal install again.

Update your Hamlet templates

Hamlet 0.7 introduces major syntax changes to all three template languages (Hamlet, Cassius and Julius). It alsoincludes a utility that will automatically convert your files to the new syntax. The following commands will overwriteyour current files, so make sure you've backed up/committed first:

hamlet6to7 *.hshamlet6to7 Handler/*.hshamlet6to7 hamlet/*.hamlethamlet6to7 cassius/*.cassiushamlet6to7 julius/*.julius

Unfortunately the tool gives a lot of false positives about mismatches in Hamlet. My best advice is to look at the diffs:this will simultaneously give you good confidence that the conversion worked correctly and get you familiar with thenew syntax.

cabal install

Even though your code won't build yet, this will be a good way to see if you've set up your cabal file correctly. Onecommon error you'll encounter is that many of the modules that used to be in the yesod package are now included inseparate packages, such as Yesod.Helpers.Static now living in yesod-static. Make sure to add in any missing packagesto your cabal file. Some likely offenders will be:• warp• yesod-form• yesod-newsfeed

Page 85: Yesod Web Framework Book

OpenTopic | Appendices | 85

• yesod-static• Possibly need to move some dependencies (like ToHtml typeclass) from hamlet to blaze-html• Control.Monad.Invert is no longer used; instead, use relevant modules from the monad-peel package (most likely

Control.Exception.Peel).

Show, Read and Eq for Html

Hamlet is now using blaze-html's Html datatype. While I think this is a good move overall, one downside is thatblaze-html does not provide Show, Read and Eq instances of Html. When you include an Html value in a Persistentmodel, it won't be able to create those instances by default. If you get error messages, just add a single "deriving" lineat the bottom. For example:

Person name String bio Html

becomes

Person name String bio Html deriving

MonadInvertIO to MonadPeelIO

Nuff said :)

urlRenderOverride

The previous scaffolded version of urlRenderOverride no longer works. Use this one instead:

urlRenderOverride a (StaticR s) = Just $ uncurry (joinPath a Settings.staticroot) $ renderRoute surlRenderOverride _ _ = Nothing

runDB: need liftIOHandler

For the impatient: stick liftIOHandler at the beginning of runDB like so: runDB db = liftIOHandler $fmap connPool getYesod >>= Settings.runConnectionPool db

For everyone else: WAI 0.2 contained the request body as an Enumerator in the Request datatype, and an Applicationwas Request -> IO Response. In WAI 0.3, an Application is Request -> Iteratee ByteStringIO Response. Long story short: all code in your Handler function now lives on top of an Iteratee monad so that itcan access the request body.

This works great most of the time. The only downside is when you need to deal with exceptions. It's impossible todefine a MonadPeelIO instance for Iteratee. Therefore, in Yesod 0.7, we have a new datatype called GGHandler,which is just a generalization of GHandler to allow an arbitrary inner monad.

GHandler defaults to having an Iteratee on the inside. But when you need a MonadPeelIO (like we do in Persistent),you need to have an IO on the inside. Eventually though, you'll need to convert your GGHandler IO to aGHandler. That's what liftIOHandler does.

mime-mail Part constructor

mime-mail introduced a new record to the Part constructor- called partHeaders- that did not exist before. To get thesame behavior as before, just pass in an empty list. This is especially relevant for users of yesod-auth's email plugin.

Page 86: Yesod Web Framework Book

86 | OpenTopic | Appendices

Feed, formerly known as AtomFeed

Patrick Brisbin has become a co-maintainer on the yesod-newsfeed package, and added a feature I'm sure manypeople will appreciate: RSS feed support. The best part is that it uses the same datatype as the Atom feeds, so withjust a few changes you can have both versions of newsfeed:

• Change AtomFeed to Feed• Change AtomFeedEntry to FeedEntry• Change records like atomTitle to feedTitle• Add two new records: feedDescription and feedLanguage• Change RepAtom to RepAtomRss• Change atomFeed to newsFeed• Change imported module to Yesod.Helpers.Feed

fileLookupDir no more

The standard line for getting static file serving previously was s = fileLookupDir Settings.staticdirtypeByExt. In Yesod 0.7, we're migrating to the much more powerful yesod-app-static package. The replacementfor the above line is s = static Settings.staticdir.

MultiParamTypeClasses

A major new feature is the simplification of the dispatch code. While this almost entirely is transparent to the user,one user-visible changes is the unification of YesodSite and YesodSubSite into YesodDispatch. As a result, yourController.hs file will now need MultiParamTypeClasses turned on.

String to ByteString

In a few places (redirectString, mime-type, sendFile) I've replaced Strings with ByteStrings. I'm not 100% certain thiswas the right choice, but it is more correct in the sense that these values are sent verbatim across the wire. A simpleData.ByteString.Char8.pack will get you the old behavior.

devel-server.hs

The code for devel-server.hs is now much simpler. Assuming your foundation datatype is MyApp, just use:

import Yesod (develServer)

main :: IO ()main = develServer 3000 "Controller" "withMyApp"

lift, not liftHandler

Thanks to ##### ###### for pointing this one out: the liftHandler function has been removed. Instead, youcan now use the standard Control.Monad.Trans.Class.lift function. Additionally, since the newIdentfunction has moved from the Widget to Handler monads, any calls you have now to newIdent will need to belifted.

Migration Guide: 0.7 to 0.8Following on the success of the previous migration guide, this chapter will similarly document the necessary changesto upgrade the Haskellers site to Yesod 0.8.

As before, if you run into issues not covered here, please add it to the comments.

Page 87: Yesod Web Framework Book

OpenTopic | Appendices | 87

No Warnings

Before you get started, I recommend you get your current code to compile without any warnings. That way, as youbegin migrating, you'll know that any new warnings are caused by the migration process itself. In fact, you may evenwant to turn on -Wall -Werror in your cabal file.

Install yesod

In theory, this will simply require running cabal update && cabal install yesod. In practice, cabalmay not be able to figure everything out for you. If you run into DLL hell, the simplest solution currently is to justdelete your .ghc folder. Please note that you need to have the happy and alex packages installed to install yesod.

Update cabal file

Almost all underlying packages for Yesod have been upgraded, including WAI, Hamlet, etc. Make sure to includenewer versions. For the most part, if you depend on the yesod package itself, you can leave off version bounds onunderlying packages. However, cabal may not always do the right thing.

Some specific notes:

• Obviously, make sure your version bounds on the yesod package allow 0.8.0. I recommend >= 0.8 && < 0.9• There was a brief period where putting language extensions in the cabal file was considered a Good Thing. We

have now reversed our decision on this.• Haskellers for 0.7 was still using FastCGI for production deployment. I'm finally switching it over to Warp this

time around. The important thing is to make sure the cpp-options: -DPRODUCTION is applied to the Warp-based executable as necessary.

• If you reference monad-peel, please replace it with monad-control.• You likely need to add the new persistent-template package to the dependencies.• Also, you'll likely need http-types.

cabal install

Even though your code won't build yet, this will be a good way to see if you've set up your cabal file correctly.

persistent-template

Some functions have moved around a bit. In particular, mkMigrate is no longer provided byDatabase.Persist.GenericSql, but by Database.Persist.TH. However, since the Yesod modulenow exports that entire module, you will likely be able to simply remove an import line.

Language Extensions

I'll assume that some people are simultaneously making the leap from GHC 6.12 to 7 (I recommend you do at thispoint, Warp performs much better on GHC 7). Here are some extension gotchas:

• You need to enable the TemplateHaskell language extension if you use any top-level TH splices. Otherwiseyou'll see Parse error: naked expression at top level

• The syntax for quasi-quotation changed in GHC 7 to no longer need the dollar sign. While you can leave it in,the compiler will give a warning. I'm keeping the dollar sign for the moment in all packages on Hackage to allowpeople to stick with GHC 6.12 for a bit longer, but in private packages, I recommend using GHC 7 syntax.

External File

This one isn't strictly necessary, but the scaffolded site is now using external files for model entity and routingdeclarations. This will allow us to develop standalone tools to make modifications to scaffolded sites. You may wantto consider doing the same thing. For example, if you have:

mkPersist [$persist|Person

Page 88: Yesod Web Framework Book

88 | OpenTopic | Appendices

name String age Int|]

You can put the entity itself in a file like entities and replace the quasi-quotation with $(persistFile"entities"). Similarly, for routing, just use the parseRoutesFile function.

toHtml

Hamlet is built on top of blaze-html. In the past, in order to convert a String to Html (and escape entities),you would use the string function. That function, and all its relatives (like text) have been deprecated in favor oftoHtml.

MonadPeelIO to MonadControlIO

Nuff said :)

String to Text

This is the big one. We've moved virtually every usage of String to strict Text. Just follow the compiler warningson this one: it's a bit of a pain to migrate this, but well worth it in potential performance improvements. TheOverloadedStrings extensions can help you big here.

It's tempting to just start packing and unpacking all over the place. I tried this as well. But the fact is, this defeats allthe possible performance benefits of the switch to Text, and is frankly harder to manage. It's much easier to just bitethe bullet, switch your datatypes from String to Text, and then clean up once.

Persist Keys

In previous versions of Persistent, all database keys were stored as integers. However, the MongoDB backend isgoing to require a different datatype (it uses 12-byte strings), so we needed to make some changes. This means thatPersistent keys are no longer instances of Integral.

For the scaffolded site from previous version of Yesod, this has an effect on the implementation of theshowAuthId, readAuthId, showAuthEmailId and readAuthEmailId functions. These functions havenow been removed, and are instead implemented internally using the SinglePiece instance for keys.

In general, if you need to serialize a key to/from text, you should use the SinglePiece instance. Most codedoesn't need to do this too often, since Yesod handles it for you via type-safe URLs. But if the need arises, usetoSinglePiece to produce a Text and fromSinglePiece to parse a Text.

Explicit Type Signatures

You may occassionally need to add explicit type signatures to deal with GHC 7's new type inferencer. This hasaffected me mostly with polymorphic Hamlet.

Page 89: Yesod Web Framework Book

OpenTopic | Examples | 89

Examples

Example: Blog<p>Well, just about every web framework I've seen starts with a blog tutorial- so here's mine! Actually, you'll see that this is actually a much less featureful blog than most, but gives a good introduction to Yesod basics. I recommend you start by <a href="/book/basics">reading the basics chapter</a>.</p>

<p>This file is literate Haskell, so we'll start off with our language pragmas and import statements. Basically every Yesod application will start off like this:</p>

> {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}> import Yesod

Next, we'll define the blog entry information. Usually, we would want to store the data in a database and allow users to modify them, but we'll simplify for the moment.

> data Entry = Entry> { entryTitle :: String> , entrySlug :: String -- ^ used in the URL> , entryContent :: String> }

Since normally you'll need to perform an IO action to load up your entries from a database, we'll define the loadEntries function to be in the IO monad.

> loadEntries :: IO [Entry]> loadEntries = return> [ Entry "Entry 1" "entry-1" "My first entry"> , Entry "Entry 2" "entry-2" "My second entry"> , Entry "Entry 3" "entry-3" "My third entry"> ]

Each Yesod application needs to define the site argument. You can use this for storing anything that should be loaded before running your application. For example, you might store a database connection there. In our case, we'll store our list of entries.

> data Blog = Blog { blogEntries :: [Entry] }> type Handler = GHandler Blog Blog

Now we use the first "magical" Yesod set of functions: mkYesod and parseRoutes. If you want to see *exactly* what they do, look at their Haddock docs. For now, we'll try to keep this tutorial simple:

> mkYesod "Blog" [parseRoutes|> / HomeR GET> /entry/#String EntryR GET> |]

Usually, the next thing you want to do after a call to mkYesod is to create an instance of Yesod. Every Yesod app needs this; it is a centralized place to define some settings. All settings but approot have sensible defaults. In

Page 90: Yesod Web Framework Book

90 | OpenTopic | Examples

general, you should put in a valid, fully-qualified URL for your approot, but you can sometimes get away with just doing this:

> instance Yesod Blog where approot _ = ""

This only works if you application is being served from the root of your webserver, and if you never use features like sitemaps and atom feeds that need absolute URLs.

We defined two resource patterns for our blog: the homepage, and the page for each entry. For each of these, we are allowing only the GET request method. For the homepage, we want to simply redirect to the most recent entry, so we'll use:

> getHomeR :: Handler ()> getHomeR = do> Blog entries <- getYesod> let newest = last entries> redirect RedirectTemporary $ EntryR $ entrySlug newest

We go ahead and send a 302 redirect request to the entry resource. Notice how we at no point need to construct a String to redirect to; this is the beauty of type-safe URLs.

Next we'll define a template for entry pages. Normally, I tend to just define them within the handler function, but it's easier to follow if they're separate. Also for clarity, I'll define a datatype for the template arguments. It would also be possible to simply use the Entry datatype with some filter functions, but I'll save that for a later tutorial.

> data TemplateArgs = TemplateArgs> { templateTitle :: Html> , templateContent :: Html> , templateNavbar :: [Nav]> }

The Nav datatype will contain navigation information (ie, the URL and title) of each entry.

> data Nav = Nav> { navUrl :: Route Blog> , navTitle :: Html> }

And now the template itself:

> entryTemplate :: TemplateArgs -> Hamlet (Route Blog)> entryTemplate args = [hamlet|> !!!> > <html>> <head>> <title>#{templateTitle args}> <body>> <h1>Yesod Sample Blog> <h2>#{templateTitle args}> <ul id="nav">> $forall nav <- templateNavbar args> <li>> <a href="@{navUrl nav}">#{navTitle nav}> <div id="content">> \#{templateContent args}> |]

Page 91: Yesod Web Framework Book

OpenTopic | Examples | 91

Hopefully, that is fairly easy to follow; if not, please review the Hamlet documentation. Just remember that dollar signs mean Html variables, and at signs mean URLs.

Finally, the entry route handler:

> getEntryR :: String -> Handler RepHtml> getEntryR slug = do> Blog entries <- getYesod> case filter (\e -> entrySlug e == slug) entries of> [] -> notFound> (entry:_) -> do> let nav = reverse $ map toNav entries> let tempArgs = TemplateArgs> { templateTitle = toHtml $ entryTitle entry> , templateContent = toHtml $ entryContent entry> , templateNavbar = nav> }> hamletToRepHtml $ entryTemplate tempArgs> where> toNav :: Entry -> Nav> toNav e = Nav> { navUrl = EntryR $ entrySlug e> , navTitle = toHtml $ entryTitle e> }

All that's left now is the main function. Yesod is built on top of WAI, so you can use any WAI handler you wish. For the tutorials, we'll use the basicHandler that comes built-in with Yesod: it serves content via CGI if the appropriate environment variables are available, otherwise with simpleserver.

> main :: IO ()> main = do> entries <- loadEntries> warpDebug 3000 $ Blog entries

Example: Ajax<p>We're going to write a very simple AJAX application. It will be a simple site with a few pages and a navbar; when you have Javascript, clicking on the links will load the pages via AJAX. Otherwise, it will use static HTML.</p>

<p>We're going to use jQuery for the Javascript, though anything would work just fine. Also, the AJAX responses will be served as JSON. Let's get started.</p>

> {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}> import Yesod> import Yesod.Helpers.Static> import Data.Monoid (mempty)

Like the blog example, we'll define some data first.

> data Page = Page> { pageName :: String> , pageSlug :: String> , pageContent :: String> }

> loadPages :: IO [Page]> loadPages = return

Page 92: Yesod Web Framework Book

92 | OpenTopic | Examples

> [ Page "Page 1" "page-1" "My first page"> , Page "Page 2" "page-2" "My second page"> , Page "Page 3" "page-3" "My third page"> ]

> data Ajax = Ajax> { ajaxPages :: [Page]> , ajaxStatic :: Static> }> type Handler = GHandler Ajax Ajax

Next we'll generate a function for each file in our static folder. This way, we get a compiler warning when trying to using a file which does not exist.

> staticFiles "static/yesod/ajax"

Now the routes; we'll have a homepage, a pattern for the pages, and use a static subsite for the Javascript and CSS files.

> mkYesod "Ajax" [$parseRoutes|> / HomeR GET> /page/#String PageR GET> /static StaticR Static ajaxStatic> |]

<p>That third line there is the syntax for a subsite: Static is the datatype for the subsite argument; siteStatic returns the site itself (parse, render and dispatch functions); and ajaxStatic gets the subsite argument from the master argument.</p>

<p>Now, we'll define the Yesod instance. We'll still use a dummy approot value, but we're also going to define a default layout.</p>

> instance Yesod Ajax where> approot _ = ""> defaultLayout widget = do> Ajax pages _ <- getYesod> content <- widgetToPageContent widget> hamletToRepHtml [$hamlet|> \<!DOCTYPE html>> > <html>> <head>> <title>#{pageTitle content}> <link rel="stylesheet" href="@{StaticR style_css}">> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">> <script src="@{StaticR script_js}">> \^{pageHead content}> <body>> <ul id="navbar">> $forall page <- pages> <li>> <a href="@{PageR (pageSlug page)}">#{pageName page}> <div id="content">> \^{pageBody content}> |]

<p>The Hamlet template refers to style_css and style_js; these were generated by the call to staticFiles above. There's nothing Yesod-specific about the <a href="/static/yesod/ajax/style.css">style.css</a> and <a href="/static/yesod/ajax/script.js">script.js</a> files, so I won't describe them here.</p>

Page 93: Yesod Web Framework Book

OpenTopic | Examples | 93

<p>Now we need our handler functions. We'll have the homepage simply redirect to the first page, so:</p>

> getHomeR :: Handler ()> getHomeR = do> Ajax pages _ <- getYesod> let first = head pages> redirect RedirectTemporary $ PageR $ pageSlug first

And now the cool part: a handler that returns either HTML or JSON data, depending on the request headers.

> getPageR :: String -> Handler RepHtmlJson> getPageR slug = do> Ajax pages _ <- getYesod> case filter (\e -> pageSlug e == slug) pages of> [] -> notFound> page:_ -> defaultLayoutJson (do> setTitle $ string $ pageName page> addHamlet $ html page> ) (json page)> where> html page = [$hamlet|> <h1>#{pageName page}> <article>#{pageContent page}> |]> json page = jsonMap> [ ("name", jsonScalar $ pageName page)> , ("content", jsonScalar $ pageContent page)> ]

<p>We first try and find the appropriate Page, returning a 404 if it's not there. We then use the applyLayoutJson function, which is really the heart of this example. It allows you an easy way to create responses that will be either HTML or JSON, and which use the default layout in the HTML responses. It takes four arguments: 1) the title of the HTML page, 2) some value, 3) a function from that value to a Hamlet value, and 4) a function from that value to a Json value.</p>

<p>Under the scenes, the Json monad is really just using the Hamlet monad, so it gets all of the benefits thereof, namely interleaved IO and enumerator output. It is pretty straight-forward to generate JSON output by using the three functions jsonMap, jsonList and jsonMap. One thing to note: the input to jsonScalar must be HtmlContent; this helps avoid cross-site scripting attacks, by ensuring that any HTML entities will be escaped.</p>

<p>And now our typical main function. We need two parameters to build our Ajax value: the pages, and the static loader. We'll load up from a local directory.</p>

> main :: IO ()> main = do> pages <- loadPages> let s = static "static/yesod/ajax"> warpDebug 3000 $ Ajax pages s

Example: Form<p>Forms can be a tedious part of web development since they require synchronization of code in many different areas: the HTML form declaration, parsing of the form and reconstructing a datatype from the raw values. The

Page 94: Yesod Web Framework Book

94 | OpenTopic | Examples

Yesod form library simplifies things greatly. We'll start off with a basic application.</p>

> {-# LANGUAGE TypeFamilies, QuasiQuotes, OverloadedStrings, MultiParamTypeClasses, TemplateHaskell #-}> import Yesod> import Control.Applicative> import Data.Text (Text)

> data FormExample = FormExample> type Handler = GHandler FormExample FormExample> mkYesod "FormExample" [$parseRoutes|> / RootR GET> |]> instance Yesod FormExample where approot _ = ""

Next, we'll declare a Person datatype with a name and age. After that, we'll create a formlet. A formlet is a declarative approach to forms. It takes a Maybe value and constructs either a blank form, a form based on the original value, or a form based on the values submitted by the user. It also attempts to construct a datatype, failing on validation errors.

> data Person = Person { name :: Text, age :: Int }> deriving Show> personFormlet p = fieldsToTable $ Person> <$> stringField "Name" (fmap name p)> <*> intField "Age" (fmap age p)

We use an applicative approach and stay mostly declarative. The "fmap name p" bit is just a way to get the name from within a value of type "Maybe Person".

> getRootR :: Handler RepHtml> getRootR = do> (res, wform, enctype) <- runFormGet $ personFormlet Nothing

<p>We use runFormGet to bind to GET (query-string) parameters; we could also use runFormPost. The "Nothing" is the initial value of the form. You could also supply a "Just Person" value if you like. There is a three-tuple returned, containing the parsed value, the HTML form as a widget and the encoding type for the form.</p>

<p>We use a widget for the form since it allows embedding CSS and Javascript code in forms directly. This allows unobtrusive adding of rich Javascript controls like date pickers.</p>

> defaultLayout $ do> setTitle "Form Example"> form <- extractBody wform

<p>extractBody returns the HTML of a widget and "passes" all of the other declarations (the CSS, Javascript, etc) up to the parent widget. The rest of this is just standard Hamlet code and our main function.</p>

> addHamlet [$hamlet|> <p>Last result: #{show res}> <form enctype="#{enctype}">> <table>> \^{form}> <tr>> <td colspan="2">> <input type="submit">> |]>

Page 95: Yesod Web Framework Book

OpenTopic | Examples | 95

> main = warpDebug 3000 FormExample

Example: Widgets> {-# LANGUAGE TypeFamilies, QuasiQuotes, OverloadedStrings, MultiParamTypeClasses, TemplateHaskell #-}> import Yesod> import Yesod.Helpers.Static> import Yesod.Form.Jquery> import Yesod.Form.Nic> import Control.Applicative> import Data.Text (unpack)> > data HW = HW { hwStatic :: Static }> type Handler = GHandler HW HW> mkYesod "HW" [$parseRoutes|> / RootR GET> /form FormR> /static StaticR Static hwStatic> /autocomplete AutoCompleteR GET> |]> instance Yesod HW where approot _ = ""> instance YesodJquery HW> instance YesodNic HW> wrapper h = [$hamlet|> <#wrapper>^{h}> <footer>Brought to you by Yesod Widgets&trade;> |]> getRootR = defaultLayout $ wrapper $ do> i <- lift newIdent> setTitle $ string "Hello Widgets"> addCassius [$cassius|> #$i$> color: red|]> addStylesheet $ StaticR $ StaticRoute ["style.css"] []> addStylesheetRemote "http://localhost:3000/static/style2.css"> addScriptRemote "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"> addScript $ StaticR $ StaticRoute ["script.js"] []> addHamlet [$hamlet|> <h1 ##{i}>Welcome to my first widget!!!> <p> <a href=@RootR@>Recursive link.> <p> <a href=@FormR@>Check out the form.> <p .noscript>Your script did not load. :(> |]> addHtmlHead [$hamlet|<meta keywords=haskell|]> > handleFormR = do> (res, form, enctype, nonce) <- runFormPost $ fieldsToTable $ (,,,,,,,,)> <$> stringField "My Field" Nothing> <*> stringField "Another field" (Just "some default text")> <*> intField "A number field" (Just 5)> <*> jqueryDayField def "A day field" Nothing> <*> timeField "A time field" Nothing> <*> boolField "A checkbox" (Just False)> <*> jqueryAutocompleteField AutoCompleteR "Autocomplete" Nothing> <*> nicHtmlField "HTML"> (Just $ string "You can put <rich text> here")> <*> maybeEmailField "An e-mail addres" Nothing> let mhtml = case res of

Page 96: Yesod Web Framework Book

96 | OpenTopic | Examples

> FormSuccess (_, _, _, _, _, _, _, x, _) -> Just x> _ -> Nothing> defaultLayout $ do> addCassius [$cassius|> .tooltip> color: #666> font-style: italic> textarea.html> width: 300px> height: 150px|]> addWidget [$hamlet|> <form method="post" enctype="#{enctype}">> <table>> \^{form}> <tr>> <td colspan="2">> \#{nonce}> <input type="submit">> $maybe html <- mhtml> \#{html}> |]> setTitle $ string "Form"> > main = warpDebug 3000 $ HW $ static "static"> > getAutoCompleteR :: Handler RepJson> getAutoCompleteR = do> term <- runFormGet' $ stringInput "term"> jsonToRepJson $ jsonList> [ jsonScalar $ unpack term ++ "foo"> , jsonScalar $ unpack term ++ "bar"> , jsonScalar $ unpack term ++ "baz"> ]

Example: Generalized HamletThis example shows how generalized hamlet templates allow the creation ofdifferent types of values. The key component here is the HamletValue typeclass.Yesod has instances for:

* Html

* Hamlet url (= (url -> [(String, String)] -> String) -> Html)

* GWidget s m ()

This example uses all three. You are of course free in your own code to makeyour own instances.

> {-# LANGUAGE QuasiQuotes, TypeFamilies, MultiParamTypeClasses, OverloadedStrings, TemplateHaskell #-}> import Yesod> data NewHamlet = NewHamlet> mkYesod "NewHamlet" [$parseRoutes|/ RootR GET|]> instance Yesod NewHamlet where approot _ = ""> type Widget = GWidget NewHamlet NewHamlet> > myHtml :: Html> myHtml = [$hamlet|<p>Just don't use any URLs in here!|]>> myInnerWidget :: Widget ()

Page 97: Yesod Web Framework Book

OpenTopic | Examples | 97

> myInnerWidget = do> addHamlet [$hamlet|> <div #inner>Inner widget> #{myHtml}> |]> addCassius [$cassius|>#inner> color: red|]> > myPlainTemplate :: Hamlet NewHamletRoute> myPlainTemplate = [$hamlet|> <p> <a href=@{RootR}>Link to home> |]> > myWidget :: Widget ()> myWidget = [$hamlet|> <h1>Embed another widget> \^{myInnerWidget}> <h1>Embed a Hamlet> \^{addHamlet myPlainTemplate}> |]> > getRootR :: GHandler NewHamlet NewHamlet RepHtml> getRootR = defaultLayout myWidget> > main :: IO ()> main = warpDebug 3000 NewHamlet

Example: Pretty YAML<p>This example uses the <a href="http://hackage.haskell.org/package/data-object-yaml">data-object-yaml package</a> to display YAML files as cleaned-up HTML. If you've read through the other tutorials, this one should be easy to follow.</p>

> {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}

> import Yesod> import Data.Object> import Data.Object.Yaml> import qualified Data.ByteString as B> import qualified Data.ByteString.Lazy as L

> data PY = PY> type Handler = GHandler PY PY

> mkYesod "PY" [$parseRoutes|> / Homepage GET POST> |]

> instance Yesod PY where approot _ = ""

> template :: Maybe (Hamlet url) -> Hamlet url> template myaml = [$hamlet|> !!!> > <html>> <head>> <meta charset="utf-8">> <title>Pretty YAML

Page 98: Yesod Web Framework Book

98 | OpenTopic | Examples

> <body>> <form method="post" action="" enctype="multipart/form-data" .>> \File name:> <input type="file" name="yaml">> <input type="submit">> $maybe yaml <- myaml> <div>^{yaml}> |]

> getHomepage :: Handler RepHtml> getHomepage = hamletToRepHtml $ template Nothing

> postHomepage :: Handler RepHtml> postHomepage = do> (_, files) <- runRequestBody> fi <- case lookup "yaml" files of> Nothing -> invalidArgs ["yaml: Missing input"]> Just x -> return x> so <- liftIO $ decode $ B.concat $ L.toChunks $ fileContent fi> hamletToRepHtml $ template $ Just $ objToHamlet so

> objToHamlet :: StringObject -> Hamlet url> objToHamlet (Scalar s) = [$hamlet|#{s}|]> objToHamlet (Sequence list) = [$hamlet|> <ul> $forall o <- list> <li>^{objToHamlet o}> |]> objToHamlet (Mapping pairs) = [$hamlet|> <dl> $forall pair <- pairs> <dt>#{fst pair}> <dd>^{objToHamlet $ snd pair}> |]

> main :: IO ()> main = warpDebug 3000 PY

Example: Internationalization> {-# LANGUAGE QuasiQuotes #-}> {-# LANGUAGE TemplateHaskell #-}> {-# LANGUAGE TypeFamilies #-}> {-# LANGUAGE MultiParamTypeClasses #-}> {-# LANGUAGE OverloadedStrings #-}

> import Yesod> import Data.Monoid (mempty)> import Data.Text (Text)

> data I18N = I18N> type Handler = GHandler I18N I18N

> mkYesod "I18N" [$parseRoutes|> / HomepageR GET> /set/#Text SetLangR GET> |]

> instance Yesod I18N where> approot _ = "http://localhost:3000"

> getHomepageR :: Handler RepHtml

Page 99: Yesod Web Framework Book

OpenTopic | Examples | 99

> getHomepageR = do> ls <- languages> let hello = chooseHello ls> let choices => [ ("en", "English") :: (Text, Text)> , ("es", "Spanish")> , ("he", "Hebrew")> ]> defaultLayout $ do> setTitle "I18N Homepage"> addHamlet [$hamlet|> <h1>#{hello}> <p>In other languages:> <ul>> $forall choice <- choices> <li>> <a href="@{SetLangR (fst choice)}">#{snd choice}> |]

> chooseHello :: [Text] -> Text> chooseHello [] = "Hello"> chooseHello ("he":_) = "Shalom"> chooseHello ("es":_) = "Hola"> chooseHello (_:rest) = chooseHello rest

> getSetLangR :: Text -> Handler ()> getSetLangR lang = do> setLanguage lang> redirect RedirectTemporary HomepageR

> main :: IO ()> main = warpDebug 3000 I18N


Recommended