HaskellにおけるHTMLのテンプレートエンジンを調査する
HaskellはWebフレームワークだけでもYesod, Snap, Happstackといった重厚長大型のフレームワークに加え、軽量なもの/API向けのものでもSpock, Scotty, Servantなど乱立しているが、Template Engineとて例外ではない。Blaze-html, shakespeare, mustache, stache, ede, lucid, heistなどこれもまた様々存在し、特にSpockやScottyといったフレームワークではテンプレートエンジン選択の自由度が高いので、どれを使おうかと思い、調べたのでここにメモを残す。
Template Engine
stackoverflowでも何を使えばよいか、議論となっている。
stache
stache: Mustache templates for Haskell
Mustacheという汎用テンプレートエンジンのhaskell実装の1つ。
ただMustache自体の仕様として記法が簡潔でよいのだが、if文の分岐がBooleanのTrue/Falseしかできないなど、少し凝ったことをしづらい面がある。
ただAesonのインスタンスをそのまま渡せるのは心強く、Viewに送りたいデータを新しいデータ型として定義し、deriveJSON
を呼んでおけば、あとはテンプレートにそのインスタンスを渡すだけでよいというのは手軽である。
記法は以下のような形。
Hi, {{name}}! You have: {{#things}} * {{.}} {{/things}}
こうすると、things
配列の要素が.
のところに順々に出力される。
Mustache
mustache: A mustache template parser library.
これも上と同じMustacheの実装。Data.AesonのValueを渡せるのはよいが、Maybe型の値のデータをみたとき、Nothingの時の値は空白が入っていることを期待したらnullが入っているというのはstacheと異なる点。そういった点で、stacheの時と同じmustacheファイルに対して同じ挙動を示すというわけではなかった。下の記事によれば、
Mustache templates in Haskell - Tutorials
it again makes simple things complex using Aeson's Value (good) and at the same time introducing its own Value type (with conflicting names of constructors and naturally not so numerous instances).
とのことで、Mustacheを使うぐらいであればstacheを使ったほうがよさそう。
ede
ede: Templating language with similar syntax and features to Liquid or Jinja2.
Mustache風味のHTMLベースのテンプレートエンジンだが、こちらのほうが表現力が高い。
こちらもData.AesonのValue型をObject型に変換し、それをテンプレートに代入してrenderしてくれる機構が備わっているので、テンプレートに与えたいデータをデータ型で用意しておけばよいのは変わらない。
--Unwrap a Value to an Object safely. fromValue :: Value -> Maybe Object
記法はこんな感じ。
<div class="checkbox"> {% for chara in characters %} <label class="checkbox-inline"> <input name="chara" value="{{chara.value.name}}" type="checkbox"> {{chara.value.name}} </label> {% endfor %} </div>
if文では、比較演算子やif-elif-else
と複数の条件で分岐させることもできるので、View側で凝ったことをすることも容易。
lucid
lucid: Clear to write, read and edit DSL for HTML
一方でこちらはhaskellの記法でHTMLを記述するDSL。
ただSpockで使う場合、テンプレートを書き換えるごとにコンパイルしなおさなければならない。ede, stacheの場合は別ファイルで呼び出すので、コンパイルをし直す必要はないが、記法が誤っている場合は実行時エラーとなる。そう考えるとCompile時にValidateしてくれるlucidや、特にshakespeareではリンクのバリデーションも施してくれるので、これらのほうが型安全であると考えられる。
Haskellの文法そのままなので、例えばリストの要素を列挙したい場合では
div_ [class_ "container"] do $ h1_ [] "List" ul_ [class_ "list-group"] $ mapM_ (li_ []) ["a", "b"]
などと書くことで、["a"], ["b"]
の中身がli
タグに囲まれて列挙される。
blaze-html
blaze-html: A blazingly fast HTML combinator library for Haskell
広く使われるHTMLライブラリではあるのですが、先述のLucidはこちらの改良版と言うか、blaze-htmlの問題点を解決したライブラリである。チュートリアルより、
import Text.Blaze.Html5 as H import Text.Blaze.Html5.Attributes as A userInfo :: Maybe User -> Html userInfo u = H.div ! A.id "user-info" $ case u of Nothing -> a ! href "/login" $ "Please login." Just user -> do "Logged in as " toHtml $ getUserName user ". Your points: " toHtml $ getPoints user
記法はこんな感じで、これの何が問題かというと、下記のブログによればいくつかあるのだが、div, id, head, mapといった要素はbaseとconflictしているのでqualifiedして呼ばなければならないし、AttributeとElementの名前がコンフリクトするものがあるので、そうするとA
やらH
やらを頭に付けなければならない。結果としてコードが見にくくなってしまうのだ。それよりかは、Lucidのようにすべてsuffixに_
をつけるほうが統一性があり、見栄えも良さそうだ。
Lucid: templating DSL for HTML
shakespeare
shakespeare: A toolkit for making compile-time interpolated templates
yesodで使われているテンプレートエンジン。hamletはこれにdeprecateされた。拡張子としては.hamlet
がHTML、.julius
がjs, .lucius
がCSSに対応している。
記法はこんな感じ。
<div .container> <h1> All Posts <div .jumbotron> <ul> $forall Entity id post <- allPosts <h4> <li> <a href=@{PostDetailsR id}>#{blogPostTitle post}
タグは閉じないで、インデントで親子関係を示す。HTMLに近い記法ができる一方でコンパイル時に変数が確実に埋め込まれる。たとえば@{}
では型付きのURLが埋め込まれるため、それが有効なURLかどうかコンパイル時にバリデートされる。これがリンク切れを起こさない秘訣だ。変数の代入は#{}
を使う。このとき、hamletではXSS attackを防ぐために適切にエスケープしてくれる。
制御構文は$
から始まる行に記述し、if
, elseif
, forall
, case
など場合分けや、Maybe型への対応なども充実している。hamletはQuasiquotesでコード内部に埋め込むこともできるし、外部ファイルに記述することもできる。外部ファイルの場合はTemplateHaskellで参照され、コンパイル時に組み込まれる。
上記はyesodの場合で、これはscreencastから持ってきた例だが、yesodと無関係に使うこともできる。
heist
heist: An Haskell template system supporting both HTML5 and XML.
snapというフレームワークで採用されているテンプレートエンジン。これは実際には試していないので、ウェブ上の例を見てみよう。
<bind tag="special">special-id</bind> <div id="${special}">very special</div> <bind tag="message">some text</bind> <p><message/></p>
heistの特徴となる主なタグはbind
とapply
で、bindはその名の通り変数を束縛し、 applyは<apply template="nav"/>
のようにすると部分テンプレートを代入することができるらしい。Haskellからどう呼び出すかというと、公式サイトの例をみると
factSplice :: Splice Snap factSplice = do input <- getParamNode let text = T.unpack $ X.nodeText input n = read text :: Int return [X.TextNode $ T.pack $ show $ product [1..n]]
というコードに対して、bindSplice "fact" factSplice templateState
とすると、<fact>
タグにfactSplice
がbindされるので、<fact>5</fact>
は120
になるとのことだ。