Yesod是一个Haskell写的web框架,用于开发类型安全、RESTful、高性能的web应用
目录
引言
Haskell
基础
莎氏模板
控件
Yesod型类
路由和处理函数
表单
会话
Persistent
部署你的web应用

莎氏模板

莎氏模板是Yesod创建HTML、CSS和Javascript的标准模板语言。这组模板语言有一些共同 的语法及总体原则:

  • 最低限度的干涉下层语言,同时在不唐突的地方提供便利。

  • 编译时保证内容合乎规范。

  • 静态类型安全,极大程度的防止 跨站脚本(XSS: cross-site scripting)攻击。

  • 尽可能通过类型安全URL来自动检查URL是否有效

Yesod并没有和这些模板语言捆绑在一起,反之亦然:它们都可以相互独立的使用。本章 会各自讲解这些模板语言,本书其余部分将使用这些模板语言来增强Yesod应用开发。

概要

这里共有四种模板语言:Hamlet是HTML模板语言,Julius是Javascript模板语言, Cassius和Lucius都是CSS模板语言。Hamlet和Cassius在格式上都对空格符敏感,用缩进 表示嵌套。相比之下,Lucius是CSS的超集,保留了CSS用括弧表示嵌套的方式。Julius几 乎就是Javascript;唯一加入的特性是变量插值。

注意
Cassius实际上只是Lucius的另一种写法。它们使用了相同的处理引擎,但Cassius文 件会在处理前将缩进转换成括弧。在它们俩中做选择完全是语法偏好问题。

Hamlet (HTML)

$doctype 5
<html>
    <head>
        <title>#{pageTitle} - My Site
        <link rel=stylesheet href=@{Stylesheet}>
    <body>
        <h1 .page-title>#{pageTitle}
        <p>Here is a list of your friends:
        $if null friends
            <p>Sorry, I lied, you don't have any friends.
        $else
            <ul>
                $forall Friend name age <- friends
                    <li>#{name} (#{age} years old)
        <footer>^{copyright}

Cassius (CSS)

#myid
    color: #{red}
    font-size: #{bodyFontSize}
foo bar baz
    background-image: url(@{MyBackgroundR})

Lucius (CSS)

section.blog {
    padding: 1em;
    border: 1px solid #000;
    h1 {
        color: #{headingColor};
    }
}

Julius (Javascript)

$(function(){
    $("section.#{sectionClass}").hide();
    $("#mybutton").click(function(){document.location = "@{SomeRouteR}";});
    ^{addBling}
});

类型

在讲解语法前,我们先看看涉及到的类型。我们在“引言”一章中讲到类型会帮我们阻止 XSS攻击。比如说,我们有一个HTML模板用来显示人名。它可能是这样的:

<p>Hello, my name is #{name}
注意
#{…}是莎氏模板里进行变量插值(variable interpolation)的语法。

name会被替换成什么,它的数据类型是什么?一个幼稚的方法是用Text类型的值,然 后逐字插入。但如果name的值是下面这样的,会给我们带来很大的问题:

<script src='http://nefarious.com/evil.js'></script>

我们希望能够对名字进行实体编码(entity-encode),因此<会变成&lt;

同样幼稚的一种办法是对每个词都做实体编码。如果你有HTML是从另一个函数生成的呢? 比如,在Yesod官网上,所有Haskell代码都被一个代码高亮函数处理过,这个函数将词嵌 套在适当的span标签内。如果我们编码所有的词,代码块将不具有可读性!

与此相反,我们有一个Html数据类型。为了生成Html值,我们有两个API可选: ToMarkup型类提供了toHtml函数,它能将StringText值转为Html,并自动转 义实体。这是我们对上面人名例子的解决方法。对于代码高亮的例子,我们要用 preEscapedToMarkup函数。

当你在Hamlet中使用变量插值时,它会对所要插入的值自动应用toHtml函数。所以如果 你插入的是一个String值,它会被转义。但如果你提供的是一个Html值,它就会保持 原样。在代码高亮例子中,我们会这样写#{preEscapedToMarkup myHaskellHtml}

注意
Html数据类型以及我们提到的几个函数,都来自于blaze-html包。这使得Hamlet 可以与所有其它blaze-html包交互,并且Hamlet能提供一种通用的生成blaze-html值的方 法。另外,我们还能充分利用blaze-html的惊人性能。

类似的,我们也有Css/ToCssJavascript/ToJavascript函数。这些会在编译时 进行一些合法性校验,以保证我们不会不小心在CSS里嵌入HTML代码。

注意

CSS模板的另一个优势是有一些颜色和单位的辅助数据类型。比如:

.red { color: #{colorRed} }

更多详情请参阅Haddock文档。

类型安全URL (Type-safe URLs)

Yesod最独特的特性可能要数类型安全URL了,正是莎氏模板使我们能够方便的使用类型安 全URL。其用法和变量插值几乎一样;我们只需要用@符号而不是#符号就可以。我们会在 稍后讲解语法;首先,让我们讲讲起因。

假设我们的应用有两条路由:http://example.com/profile/home是首页, http://example.com/display/time显示当前时间。假设我们想从首页链接到时间页 面。我能想到三种不同的构造URL的方法:

  1. 相对路径:../display/time

  2. 绝对路径,不包含域名:/display/time

  3. 绝对路径,包含域名:http://example.com/display/time

这三种方法都有各自的问题:第一种方法只要其中有一个URL改变了,就会失效。同样的 ,它不适用于所有的应用场合;比如说RSS和Atom源需要的是绝对路径。第二种方法比第 一种方法更能抵抗变化,但还是不能用于RSS和Atom源。虽然第三种方法能适用于所有应 用场合,但如果你的域名发生改变,你就需要去更新应用里的每一个URL。你觉得这不会 经常发生?等你从开发环境迁移到升级(staging)环境再迁移到生产环境就知道了。

但更为重要的是,这些方法都有个大问题,如果你修改了你的路由,编译器不会对失效链 接发出警告。更不用说拼写错误都能造成严重破坏。

类型安全URL的目的是尽量让编译器为我们检查。为了做到这一点,我们的第一步是必须 放弃使用普通文本,而是用明确定义的数据类型,因为普通文本是编译器所不能理解的。 对于这个简单的应用,我们用一个汇总类型(sum type)来建模我们的路由。

data MyRoute = Home | Time

我们在模板中不使用/display/time这样的链接,而是使用Time这个构造函数。但最终 ,HTML还是由文本构成的,而不是数据类型,所以我们需要能将这些值转为文本的方法。 我们称之为URL呈现函数(rendering function),下面是一个简单的样例:

renderMyRoute :: MyRoute -> Text
renderMyRoute Home = "http://example.com/profile/home"
renderMyRoute Time = "http://example.com/display/time"
注意

URL呈现函数实际上比这个要复杂一些。它们需要表示请求参数(query string parameters)、处理构造函数中的记录(record),并且智能的处理域名。但实际上 ,你不需要担心这些,因为Yesod会自动为你生成URL呈现函数。需要指出的一点是,为了 处理请求参数,类型标识(type signature)会略微复杂:

type Query = [(Text, Text)]
type Render url = url -> Query -> Text
renderMyRoute :: Render MyRoute
renderMyRoute Home _ = ...
renderMyRoute Time _ = ...

好了,我们在有了呈现函数,在模板里有了类型安全的URL。这些究竟是怎么结合在一起 的呢?我们不是直接生成Html(或CssJavascript),莎氏模板实际上生成的是一 个函数,它的输入是呈现函数,输出是HTML。为了看清楚这点,我们看一个(假的) Hamlet底层细节。假设我们有这样一个模板:

<a href=@{Time}>The time

它大概可以翻译成如下Haskell代码:

\render -> mconcat ["<a href='", render Time, "'>The time</a>"]

语法

所有莎氏语言都共用一套插值语法,并且都能够使用类型安全的URL。它们的差异体现在 与其目标语言(HTML、CSS、Javascript)相对应的语法上。

Hamlet语法

Hamlet是莎氏模板语言里最复杂的。不仅因为它提供了生成HTML的语法,它也允许简单的 控制结构:条件、循环和或许(maybe)。

标签(Tags)

显然,标签在任何HTML模板语言中都起重要作用。在Hamlet中,我们试图与现有HTML语法 保持高度一致,这样使用起来更自然。然而,我们使用缩进来表示嵌套,而不用关闭标签 。因此这样的HTML代码:

<body>
<p>Some paragraph.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</body>

对应的Hamlet代码是

<body>
    <p>Some paragraph.
    <ul>
        <li>Item 1
        <li>Item 2

通常来说,我们发现你习惯Hamlet以后会写起来会比HTML更容易。唯一有点技巧的地方是 处理标签前后的空格字符。比如,如果我们要创建这样的HTML

<p>Paragraph <i>italic</i> end.</p>

我们想确保“Paragraph“后和“end”前的空格符被保留。为了做到这一点,我们使用两个简 单的转义字符:

<p>
    Paragraph #
    <i>italic
    \ end.

空格转义字符实际上非常简单:

  1. 如果一行的第一个非空字符是反斜线(\),则反斜线被忽略。

  2. 如果一行的最后一个字符是#号,则#号被忽略。

另外,Hamlet*不*对内容中的实体(entity)进行转义。这么做是有意的,是为了让拷贝已 有HTML代码到Hamlet更加容易。因此上面的例子也可以写成这样:

<p>Paragraph <i>italic</i> end.

注意,这样写的话第一个标签(<p>)会由Hamlet自动关闭,而嵌套的<i>不会。用哪种写法 你可以自由选择,它们都没有什么负面影响。然而当心,你在Hamlet中*只*能对这些内联 标签使用关闭标签;正常的标签不能手动关闭。

插值

到目前为止,我们有一个不错、简化的HTML实现,但它还完全不能和Haskell代码交互。 我们怎么传入变量呢?很简单:用变量插值:

<head>
    <title>#{title}

#号后紧跟一对花括号表示这是一个*变量插值*。在这面这个例子中,被插入的值是调用 模板时作用域内的title变量值。让我再说一次:Hamlet在被调用时,自动拥有了访问 作用域内变量的权限。不需要特意将变量传入。

你可以在变量插值中调用函数。也可以使用字符串和数字。可以使用限定模块(qualified modules)。可以用括号和美元符号来组合表达式(statements)。最后,toHtml函数会作 用在结果上,意味着任何ToHtml的实例都可以做插值。比如,下面的代码。

-- 暂时忽略准引用(quasiquote)和shamlet。后面后讲解。
{-# LANGUAGE QuasiQuotes #-}
import Text.Hamlet (shamlet)
import Text.Blaze.Html.Renderer.String (renderHtml)
import Data.Char (toLower)
import Data.List (sort)

data Person = Person
    { name :: String
    , age  :: Int
    }

main :: IO ()
main = putStrLn $ renderHtml [shamlet|
<p>Hello, my name is #{name person} and I am #{show $ age person}.
<p>
    Let's do some funny stuff with my name: #
    <b>#{sort $ map toLower (name person)}
<p>Oh, and in 5 years I'll be #{show ((++) 5 (age person))} years old.
|]
  where
    person = Person "Michael" 26

我们大肆吹捧的类型安全URL是怎样(做插值)的呢?他们和变量插值几乎是一样的,除了 它们是以@符开头。另外,^符允许你插入另一个同类模板。下面的代码示例能说明这两种 插值。

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import Text.Hamlet (HtmlUrl, hamlet)
import Text.Blaze.Html.Renderer.String (renderHtml)
import Data.Text (Text)

data MyRoute = Home

render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"

footer :: HtmlUrl MyRoute
footer = [hamlet|
<footer>
    Return to #
    <a href=@{Home}>Homepage
    .
|]

main :: IO ()
main = putStrLn $ renderHtml $ [hamlet|
<body>
    <p>This is my page.
    ^{footer}
|] render
属性

在上一个例子中,我们给“a“标签赋予了href属性值。让我们来讲讲(属性的)语法:

  • 你可以在属性值中使用变量插值。

  • 等号和属性值是可选的,就像在HTML里那样。因此<input type=checkbox checked> 是完全合法的。

  • 有两种便捷的属性定义方法:对于id,你可以用#号,对于class,你可以用点(.)。换 句话说,(可以这样定义:)<p #paragraphid .class1 .class2>

  • 属性值两边的引号是可选的,但如果你要属性值内有空格,则引号是必须的。

  • 你可以用冒号来选择性的增加属性。要让一个复选框(checkbox)仅当isChecked变量为 真时才被选中,可以这样写<input type=checkbox :isChecked:checked。要让一段 文字选择性为红色,你可以用<p :isRed:style="color:red">

条件语句

最终,你会想要在页面上增加(控制)逻辑。Hamlet的目标是尽量简化逻辑,将繁重的任务 交给Haskell代码。因此,我们的逻辑语句非常基础… 基础到只有ifelseifelse

$if isAdmin
    <p>Welcome to the admin section.
$elseif isLoggedIn
    <p>You are not the administrator.
$else
    <p>I don't know who you are. Please log in so I can decide if you get access.

普通的插值规则,同样也可以用在条件语句中。

或许(Maybe)

类似的,我们也有专门的结构来处理Maybe值。技术上它们可以用ifisJustfromJust来实现,但这样(的专门结构)更方便,也避免了偏函数(partial functions) 。

$maybe name <- maybeName
    <p>Your name is #{name}
$nothing
    <p>I don't know your name.

在(←)左侧,你除了可以用简单的标识符,还可以使用更复杂的值,比如构造函数和元组 (tuple)。

$maybe Person firstName lastName <- maybePerson
    <p>Your name is #{firstName} #{lastName}

(←)右侧遵循与插值相同的规则,允许使用变量、调用函数等。

Forall语句

怎么对列表做循环呢?我们也有相应的结构:

$if null people
    <p>No people.
$else
    <ul>
        $forall person <- people
            <li>#{person}
Case语句

模式匹配是Haskell的一大强项。汇合类型(Sum types)让你能清晰的建模真实世界的类型 ,case语句让你安全的做模式匹配,如果你忘了处理某一种情况,编译器会发出警告。 Hamlet也能做到这点。

$case foo
    $of Left bar
        <p>It was left: #{bar}
    $of Right baz
        <p>It was right: #{baz}
With语句

我们可以用with来概括一个语句。这样做基本上是为了给一条长语句声明一个别名。

$with foo <- some very (long ugly) expression that $ should only $ happen once
    <p>But I'm going to use #{foo} multiple times. #{foo}
Doctype语句

最后一个语法糖:doctype语句。我们支持不同版本的doctype,不过我们给时下大多数 web应用推荐$doctype 5,它会生成<!DOCTYPE html>

$doctype 5
<html>
    <head>
        <title>Hamlet is Awesome
    <body>
        <p>All done.
注意
还有一种旧的但仍被支持的语法:三个感叹号(!!!)。你可能在代码里还见过这 种用法。我们不打算移除它,但通常来说,用$doctype更易读。

Lucius语法

Lucius是莎氏模板里两种CSS模板语言之一。它被设计成CSS的超集,依托现有的CSS语法 ,同时加入更多功能。

  • 与Hamlet类似,我们允许变量和URL插值。

  • 允许嵌套CSS块。

  • 可以在模板中定义变量。

  • 可以将一组CSS属性定义成mixin,然后多处复用。

从第二点开始:假设你想对article标签内的几个标签应用特殊的样式。用普通的CSS, 你必须这样写:

article code { background-color: grey; }
article p { text-indent: 2em; }
article a { text-decoration: none; }

这个例子虽然只有几句话,但每次都必须打出article还是有点讨厌。想象你有十几个这 样的语句。虽然不是最糟糕的事,但还是有点烦人。Lucius可以帮到你:

article {
    code { background-color: grey; }
    p { text-indent: 2em; }
    a { text-decoration: none; }
}

Lucius变量可以让你避免重复。一个简单的例子是定义共用的颜色:

@textcolor: #ccc; /* just because we hate our users */
body { color: #{textcolor} }
a:link, a:visited { color: #{textcolor} }

Mixin是Lucius较新的特性。它的基本思想是将一组属性声明为一个mixin,然后在模板中 使用^做嵌套。下面的例子说明了我们可以怎样用mixin来处理浏览器前缀(vendor prefix)的问题。

{-# LANGUAGE QuasiQuotes #-}
import Text.Lucius
import qualified Data.Text.Lazy.IO as TLIO

-- 假的呈现函数。
render = undefined

-- 我们的mixin,为transition属性提供了很多浏览器前缀。
transition val =
    [luciusMixin|
        -webkit-transition: #{val};
        -moz-transition: #{val};
        -ms-transition: #{val};
        -o-transition: #{val};
        transition: #{val};
    |]

-- 我们实际的Lucius模板,其中使用了mixin。
myCSS =
    [lucius|
        .some-class {
            ^{transition "all 4s ease"}
        }
    |]

main = TLIO.putStrLn $ renderCss $ myCSS render

Cassius语法

Cassius是空格敏感的Lucius版本。在概要中我们已经提到,它和Lucius使用相同的处理 引擎,不过在预处理的时候会在代码块前后插入括弧,在每行末尾插入分号。这意味着你 在写Cassius的时候可以用上Lucius的所有特性。下面是一个简单的例子:

#banner
    border: 1px solid #{bannerColor}
    background-image: url(@{BannerImageR})

Julius Syntax

Julius是这四种语言里最简单的。实际上,有人甚至会说它就是Javascript。Julius可以 使用我们提过的三种形式的插值,除此以外,不会对内容做任何转换。

注意
如果你在Yesod脚手架站点中使用了Julius,你可能会发现你的Javascript被自动 压缩了。这不是Julius的特性;而是因为Yesod使用了hjsmin包来压缩Julius的输出。

调用莎氏模板

问题来了:我到底怎么使用这些模板呢?有三种方法可以从Haskell代码调用莎氏模板:

准引用

准引用允许你在Haskell代码中嵌套任意内容,并且在编译时将其转换为 Haskell代码。

外部文件

这种情况下,模板代码保存在外部文件中,通过Haskell模板来1调用。

重载模式

上面两种方法在修改代码后都需要完全重编译。在重载模式中,你的模板放 在外部文件里,通过Haskell模板调用。但在运行时,外部文件每次都重新解析。

注意
Hamlet不能使用重载模式,Cassius、Lucius和Julius可以。Hamlet里有太多复杂 的功能直接依赖于Haskell编译器,因此无法在运行时重新实现。

在生产环境中,应该使用前两种方法。它们都把模板整个嵌入最后的可执行文件中,简化 了部署,优化了性能。准引用的好处是简单:所有内容在一个文件中。对于内容少的模板 这样很好。然而,一般都推荐外部文件的方法,因为:

  • 它遵循了将逻辑层与表示层分离的惯例。

  • 你可以方便的用CPP宏在外部文件和调试(重载)模式间切换,也就是说你既可以进行快 速开发,也能在生产环境得到高性能。

由于用到了特殊的准引用和Haskell模板函数,你需要确保启用了相应的语言扩展,并正 确的使用语法。你可以在下面的代码里看到这两种用法。

准引用
{-# LANGUAGE OverloadedStrings #-} -- 我们下面要用Text类型
{-# LANGUAGE QuasiQuotes #-}
import Text.Hamlet (HtmlUrl, hamlet)
import Data.Text (Text)
import Text.Blaze.Html.Renderer.String (renderHtml)

data MyRoute = Home | Time | Stylesheet

render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"
render Time _ = "/time"
render Stylesheet _ = "/style.css"

template :: Text -> HtmlUrl MyRoute
template title = [hamlet|
$doctype 5
<html>
    <head>
        <title>#{title}
        <link rel=stylesheet href=@{Stylesheet}>
    <body>
        <h1>#{title}
|]

main :: IO ()
main = putStrLn $ renderHtml $ template "My Title" render
外部文件
{-# LANGUAGE OverloadedStrings #-} -- 我们下面要用Text类型
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE CPP #-} -- 在生产环境和调试(开发)模式间切换
import Text.Lucius (CssUrl, luciusFile, luciusFileDebug, renderCss)
import Data.Text (Text)
import qualified Data.Text.Lazy.IO as TLIO

data MyRoute = Home | Time | Stylesheet

render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"
render Time _ = "/time"
render Stylesheet _ = "/style.css"

template :: CssUrl MyRoute
#if PRODUCTION
template = $(luciusFile "template.lucius")
#else
template = $(luciusFileDebug "template.lucius")
#endif

main :: IO ()
main = TLIO.putStrLn $ renderCss $ template render

{- @template.lucius

foo { bar: baz }

-}

(调用莎氏模板的)函数有统一的命名规则。

模板语言 准引用 外部文件 重载模式

Hamlet

hamlet

hamletFile

Cassius

cassius

cassiusFile

cassiusFileReload

Lucius

lucius

luciusFile

luciusFileReload

Julius

julius

juliusFile

juliusFileReload

其它Hamlet类型

目前为止,我们已经看到如何从Hamlet生成HtmlUrl值,它是一段内嵌类型安全URL的 HTML代码。还有三种值可以用Hamlet生成:普通的HTML、含URL*以及*多语言消息的HTML 、控件。用Hamlet生成控件会在“控件”一章中讲解。

要生成不含URL的普通HTML,我们就使用“简化的Hamlet“。它(与普通的Hamlet相比)有一 些改动:

  • 我们使用另一组以字母“s”打头的函数。因此准引用名为shamlet,外部文件模板函数 为shamletFile。至于这些函数怎么发音仍存争议。

  • 不允许URL插值。URL插值会导致编译错误。

  • ^插值不允许任意的HtmlUrl值。被嵌套的值必须与模板本身的类型一致,所以它必须 也是Html类型。这意味着对于shamlet,内嵌可以完全用普通的变量插值(用#号)替代 。

在Hamlet中处理多国语言(i18n)会有些复杂。Hamlet通过消息数据类型来支持i18n,与类 型安全URL在概念和实现上都非常近似。比如说,我们想要让应用对你说hello并告诉你吃 掉的苹果数。我们可以用这样的数据类型表示消息。

data Msg = Hello | Apples Int

然后,我们希望能将它转换为可读文本,因此定义一些呈现函数:

renderEnglish :: Msg -> Text
renderEnglish Hello = "Hello"
renderEnglish (Apples 0) = "You did not buy any apples."
renderEnglish (Apples 1) = "You bought 1 apple."
renderEnglish (Apples i) = T.concat ["You bought ", T.pack $ show i, " apples."]

现在我们想把这些Msg值直接插入模板。我们要用到下划线插值方法。

$doctype 5
<html>
    <head>
        <title>i18n
    <body>
        <h1>_{Hello}
        <p>_{Apples count}

还需要能将这样的模板转换为HTML的方法。因此就像类型安全URL,我们传入一个呈现 函数。为了表示它,我们定义一个新的类型别名:

type Render url = url -> [(Text, Text)] -> Text
type Translate msg = msg -> Html
type HtmlUrlI18n msg url = Translate msg -> Render url -> Html

至此,我们可以将renderEnglishrenderSpanishrenderKlingon传入模板,它 会输出翻译好的结果(当然,取决于翻译得有多好)。完整的程序是:

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Text (Text)
import qualified Data.Text as T
import Text.Hamlet (HtmlUrlI18n, ihamlet)
import Text.Blaze.Html (toHtml)
import Text.Blaze.Html.Renderer.String (renderHtml)

data MyRoute = Home | Time | Stylesheet

renderUrl :: MyRoute -> [(Text, Text)] -> Text
renderUrl Home _ = "/home"
renderUrl Time _ = "/time"
renderUrl Stylesheet _ = "/style.css"

data Msg = Hello | Apples Int

renderEnglish :: Msg -> Text
renderEnglish Hello = "Hello"
renderEnglish (Apples 0) = "You did not buy any apples."
renderEnglish (Apples 1) = "You bought 1 apple."
renderEnglish (Apples i) = T.concat ["You bought ", T.pack $ show i, " apples."]

template :: Int -> HtmlUrlI18n Msg MyRoute
template count = [ihamlet|
$doctype 5
<html>
    <head>
        <title>i18n
    <body>
        <h1>_{Hello}
        <p>_{Apples count}
|]

main :: IO ()
main = putStrLn $ renderHtml
     $ (template 5) (toHtml . renderEnglish) renderUrl

其它莎氏模板

除了用于生成HTML、CSS和Javascript,还有一些特殊用途的莎氏模板。 shakespeare-text包提供了一种创建文本插值的简单方法,它与Ruby和Python这些脚本语 言中的用法非常相似。这个包的用途绝不限于Yesod。

{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
import Text.Shakespeare.Text
import qualified Data.Text.Lazy.IO as TLIO
import Data.Text (Text)
import Control.Monad (forM_)

data Item = Item
    { itemName :: Text
    , itemQty :: Int
    }

items :: [Item]
items =
    [ Item "apples" 5
    , Item "bananas" 10
    ]

main :: IO ()
main = forM_ items $ \item -> TLIO.putStrLn
    [lt|You have #{show $ itemQty item} #{itemName item}.|]

关于这个例子的几点快速说明:

  • 注意我们涉及到三种不同的文本数据类型(String,strict Text和lazy Text)。 它们可以混合使用。

  • 我们使用了叫lt的准引用,它能生成lazy文本。相应的有st

  • 另外,这两个准引用也有较长名字的版本(ltextstext)。

一般建议

以下是来自Yesod社区关于最有效使用莎氏模板的几点提示。

  • 对于真实的网站,使用外部文件。对于类库,如果模板不长的话,可以使用准引用。

  • Patrick Brisbin整理了一个 (莎氏模板)Vim代码高亮 脚本,会很有帮助。

  • 你应该总是给每个Hamlet标签另起一行,而不要在现有标签中嵌套开始/关闭标签。唯 一的例外是大段文本中偶尔的<i><b>标签。