「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."

输入为:


Natural numbers

A list of natural numbers:

A foo image."

解读

blaze-html 最大的特色,就是用简洁的语法表达了 Html 元素之间的组合关系。其中,又以 do 语法糖的使用最为精彩和诡异,而整体实现方式又相对简单易懂。接下来,我们就来打开 blaze-html 的代码,看看这种简洁是怎么做到的:

  • 简洁性
    • 嵌套关系:函数类型 Html -> Html

        body :: Html  -- ^ Inner HTML.
             -> Html  -- ^ Resulting HTML.
        body = Parent "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 信息。因为不符合常理的 >> 才是作者希望我们去使用的。
    • 属性设置:(!) 同时支持 HtmlHtml -> 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)

实际上,HtmlMonad 实例不满足以上任意一条。这样的结果是,没有办法定义 Monad transformer HtmlT ,所以也就没有办法和其他 Monad 混合使用。比如,理想实现下,我们可以用包一层 ReaderT 来实现模板变量的功能

应用

作为一个高效的 Html combinator 函数库,blaze-html 的应用面还是很广的。比如,Shakespeare 的 Hamlet 模板,就使用了 blaze-html 的 Html 类型作为自己的最终输出。

你可能感兴趣的:(「blaze-html」Day 1. - 30天Hackage之旅)