30天Hackage之旅凉倚,第一天獻(xiàn)給 blaze-html 這個(gè)『賊快』(blazing fast) 的 HTML combinator。
示例
blaze-html 讓我們可以用類似前端里 pug 的語法來寫 HTML旨涝,HTML代碼的層級(jí)關(guān)系通過 do
表達(dá)得非常清楚,同時(shí)屬性的書寫也通過 (!)
得到了很好的支持
{-# 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 最大的特色侣背,就是用簡(jiǎn)潔的語法表達(dá)了 Html 元素之間的組合關(guān)系白华。其中慨默,又以 do
語法糖的使用最為精彩和詭異,而整體實(shí)現(xiàn)方式又相對(duì)簡(jiǎn)單易懂弧腥。接下來厦取,我們就來打開 blaze-html 的代碼,看看這種簡(jiǎn)潔是怎么做到的:
- 簡(jiǎn)潔性
-
嵌套關(guān)系:函數(shù)類型
Html -> Html
body :: Html -- ^ Inner HTML. -> Html -- ^ Resulting HTML. body = Parent "body" "<body" "</body>"
當(dāng)我們調(diào)用
body $ p "foo"
時(shí)管搪,實(shí)際上是把一個(gè)Html
類型的p "foo"
為給了類型為Html -> Html
的函數(shù)body
并列關(guān)系:作為
Monad
實(shí)例虾攻,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
的構(gòu)造器之一Append
更鲁,即將兩個(gè)并列的Html
進(jìn)行拼接台谢。相當(dāng)于在do
語法糖里的任意上下兩句,都會(huì)被Append
包裹起來岁经,從而達(dá)到并列的效果。
反觀>>=
蛇券,blaze-html 的作者并不希望我們?nèi)ナ褂?>>=
缀壤,在這里直接給出了 error 信息。因?yàn)椴环铣@淼?>>
才是作者希望我們?nèi)ナ褂玫摹?- 屬性設(shè)置:
(!)
同時(shí)支持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
, 從上面兩個(gè)instance Attributable
可以看到塘慕,對(duì)于Html
類型的 h (比如img
),直接調(diào)用f h
即可蒂胞;而對(duì)于Html -> Html
類型的 h (比如p
)图呢,則只需要做函數(shù)的組合,即等到h
返回一個(gè)包含自身及子元素的內(nèi)容后骗随,再附加上屬性值蛤织。 而img ! src "foo" ! alt "bar"
這樣的嵌套使用也就非常自然了。 -
問題
在我們肯定 blaze-html 提供的簡(jiǎn)潔性的同時(shí)鸿染,我們也需要注意指蚜,雖然 Html
的 >>
和對(duì)應(yīng)的 do
語法糖很好用,但其實(shí)這樣的定義并不能算是真正的 Monad
, 因?yàn)檫@違反了 Monad Laws:
Left identity: return a >>= f ≡ f a
Right identity: m >>= return ≡ m
Associativity: (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
實(shí)際上涨椒,Html
的 Monad
實(shí)例不滿足以上任意一條摊鸡。這樣的結(jié)果是,沒有辦法定義 Monad transformer HtmlT
蚕冬,所以也就沒有辦法和其他 Monad
混合使用免猾。比如,理想實(shí)現(xiàn)下囤热,我們可以用包一層 ReaderT
來實(shí)現(xiàn)模板變量的功能
應(yīng)用
作為一個(gè)高效的 Html combinator 函數(shù)庫(kù)猎提,blaze-html 的應(yīng)用面還是很廣的。比如赢乓,Shakespeare 的 Hamlet 模板忧侧,就使用了 blaze-html 的 Html
類型作為自己的最終輸出石窑。