美文网首页我爱编程
「blaze-html」Day 1. - 30天Hackage之

「blaze-html」Day 1. - 30天Hackage之

作者: kdepp | 来源:发表于2016-11-11 01:06 被阅读45次

    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 信息。因为不符合常理的 >> 才是作者希望我们去使用的。
      • 属性设置:(!) 同时支持 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 的应用面还是很广的。比如,ShakespeareHamlet 模板,就使用了 blaze-html 的 Html 类型作为自己的最终输出。

    相关文章

      网友评论

        本文标题:「blaze-html」Day 1. - 30天Hackage之

        本文链接:https://www.haomeiwen.com/subject/ehoxpttx.html