「blaze-html」Day 1. - 30天Hackage之
30天Hackage之旅,第一天献给 blaze-html 这个『贼快』(blazing fast) 的 HTML combinator。
示例
blaze-html 让我们可以用类似前端里 pug 的语法来写 HTML,HTML代码的层级关系通过 do
表达得非常清楚,同时属性的书写也通过 (!)
得到了很好的支持
{-# LANGUAGE OverloadedStrings #-}
import Text.Blaze.Html5 as H5 hiding (main)
import Text.Blaze.Html5.Attributes as A
import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
main :: IO ()
main = print $ renderHtml demo
demo = docTypeHtml $ do
H5.head $ do
H5.title "Natural numbers"
body $ do
p "A list of natural numbers:"
img ! src "foo.png" ! alt "A foo image."
输入为:
<!DOCTYPE HTML>
<html><head><title>Natural numbers</title></head><body><p>A list of natural numbers:</p><img src="foo.png" alt="A foo image."></body></html>"
解读
blaze-html 最大的特色,就是用简洁的语法表达了 Html 元素之间的组合关系。其中,又以 do
语法糖的使用最为精彩和诡异,而整体实现方式又相对简单易懂。接下来,我们就来打开 blaze-html 的代码,看看这种简洁是怎么做到的:
- 简洁性
-
嵌套关系:函数类型
Html -> Html
body :: Html -- ^ Inner HTML. -> Html -- ^ Resulting HTML. body = Parent "body" "<body" "</body>"
当我们调用
body $ p "foo"
时,实际上是把一个Html
类型的p "foo"
为给了类型为Html -> Html
的函数body
-
并列关系:作为
Monad
实例,Html
类型具有特殊的>>
这里,instance Monad MarkupM where return _ = Empty (>>) = Append h1 >>= f = h1 >> f (error "Text.Blaze.Internal.MarkupM: invalid use of monadic bind") type Markup = MarkupM ( ) type Html = Markup
>>
直接等同于MarkupM
的构造器之一Append
,即将两个并列的Html
进行拼接。相当于在do
语法糖里的任意上下两句,都会被Append
包裹起来,从而达到并列的效果。
反观>>=
,blaze-html 的作者并不希望我们去使用>>=
,在这里直接给出了 error 信息。因为不符合常理的>>
才是作者希望我们去使用的。- 属性设置:
(!)
同时支持Html
和Html -> Html
可以看到,newtype Attribute = Attribute (forall a. MarkupM a -> MarkupM a) class Attributable h where (!) :: h -> Attribute -> h instance Attributable (MarkupM a) where h ! (Attribute f) = f h instance Attributable (MarkupM a -> MarkupM b) where h ! f = (! f) . h
Attribute
本身就是Html -> Html
, 从上面两个instance Attributable
可以看到,对于Html
类型的 h (比如img
),直接调用f h
即可;而对于Html -> Html
类型的 h (比如p
),则只需要做函数的组合,即等到h
返回一个包含自身及子元素的内容后,再附加上属性值。 而img ! src "foo" ! alt "bar"
这样的嵌套使用也就非常自然了。 -
问题
在我们肯定 blaze-html 提供的简洁性的同时,我们也需要注意,虽然 Html
的 >>
和对应的 do
语法糖很好用,但其实这样的定义并不能算是真正的 Monad
, 因为这违反了 Monad Laws:
Left identity: return a >>= f ≡ f a
Right identity: m >>= return ≡ m
Associativity: (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
实际上,Html
的 Monad
实例不满足以上任意一条。这样的结果是,没有办法定义 Monad transformer HtmlT
,所以也就没有办法和其他 Monad
混合使用。比如,理想实现下,我们可以用包一层 ReaderT
来实现模板变量的功能
应用
作为一个高效的 Html combinator 函数库,blaze-html 的应用面还是很广的。比如,Shakespeare 的 Hamlet 模板,就使用了 blaze-html 的 Html
类型作为自己的最终输出。