星の贈り物

(テキスト未定)

2012年12月の日記

2012年12月6日

本物のプログラマはHaskellを使う 第57回掲載

[topic:Haskell]

ITpro に 本物のプログラマはHaskellを使う - 第57回 機能テストや性能テストをCabalで自動化 が掲載されました。

今回は、これまでの回で説明した機能テストを行うプログラムや性能テストを行うプログラムを、Cabal を使って自動化する方法について説明します。また、機能テストを自動化する例では、コード網羅率を調べるための HPC と呼ばれるツールを利用する方法についても説明します。


2012年12月8日

型付き S 式 XML

[topic:Haskell]

これは Haskell Advent Calendar 2012 8日目の記事です。

みなさんは HTML 文書や XML 文書などを作成する際、どのような書式を使っているでしょうか? ベタに手書きしているでしょうか? Markdown やブログ独自の書式、Wiki 記法などの軽量のマークアップ言語を使っているでしょうか? Haskell などの言語上に構築した DSL を使っているでしょうか? それとも Pandoc を使って他の文書から変換しているでしょうか?

私は、今、 HSXML という Haskell の言語内 DSL を使ってこの文章を書いています。HSXML は、 SXML と呼ばれる「S 式を使って XML を表現する形式」の Haskell 版です。( HSXML のソースコード ) ……と、言葉だけで書いても分からないと思うので、例を見てみましょう。


{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import HSXML
import HSXML_doc
import HSXML_ext (title)
import HSXML_HTML
import Prelude hiding (head, div)

testDoc =
    (document
     (head
      (title "buildbox を使おう")
      (meta_tag (description "generated HTML")))
    (body
     (h1 "buildbox の紹介")
     (div (attr (title "buildbox の紹介と使用例"))
      (p
       (a (attr (href (URL "http://hackage.haskell.org/package/buildbox")))
         "buildbox")
       "は、" (strong "Haskell 製の継続的インテグレーションツール") "です。"
      )
      (p
       "buildbox は"
       (a (attr (href (URL "http://disciple.ouroborus.net/wiki/Development/Building")))
         "ddc という言語処理系の開発")
       "や"
       (a (attr (href (URL "https://github.com/AccelerateHS/accelerate-buildbot")))
         "accelerate というライブラリの開発")
       "などで使われています。"
       "また、現在も継続して使われているかどうか分かりませんが、"
       (a (attr (href (URL "https://github.com/ghc/packages-dph/tree/master/dph-buildbot"))
                (title "GHC が darcs で開発されていた時代の DPH ライブラリと repa の buildbot"))
         "DPH の開発")
       "や"
       (a (attr (href (URL "http://stackoverflow.com/questions/4000005/lightweight-continuous-integration-for-a-centrally-haskell-darcs-toolchain"))
                (title "今は亡き repa 専用の buildbot に関する情報"))
         "repa の開発")
       "にも使われていました。"
      ))
      (p "なお、GHC の開発には、"
       (a (attr (href (URL "http://hackage.haskell.org/trac/ghc/wiki/Builder")))
         "builder")
       "と呼ばれる独自のツールが使われています。")
      (hr)))

main = runHTMLIO $ run_root testDoc

a (DC attrs :: DC CT_block d) body = inline_inline tr mempty body
 where tr (DC body) = cdata_sep $
         DC $ emit_elem "a" (Just attrs) (Just body) 

testDoc の定義が S 式になっていますね。この S 式の部分が HSXML です。main 変数では、この HSXML から HTML 文書を生成しています。

HSXML で使われている要素や属性などのタグは、全て Haskell の関数です。Haskell の関数なので、ただのS式とは違い「ブロック(レベル)要素として扱われる、インライン要素として扱われる、属性として扱われる、ブロック要素を持てる、インライン要素を持てる」などといった型があります。

例えば a 要素は、「属性とインライン要素を持つ、インライン要素」として定義されています。


*Main> :t a
a :: (MDoc d, Build (DC CT_inline d) t,
      BuildR t  DC CT_inline d) =>
     DC CT_block d -> DC CT_inline d -> t
*Main> :m HSXML
Prelude HSXML> :t attr
attr
  :: (HSXML_doc.MDoc d, Build (DC CT_battr d) r,
      BuildR r  DC CT_block d) =>
     DC CT_battr d -> r
Prelude HSXML> :t href
href :: HSXML_doc.MDoc d => HSXML_doc.URL -> DC CT_battr d 

同様に div 要素は「属性とブロック要素を持つ、ブロック要素」、p 要素は「インライン要素を持つインライン要素」として定義されています。


-- The body of P has the content Inline, but the P element itself
-- is in the Block context
p x = block_inline (wrap_tag "p") mempty x

〜 略 〜

-- HTML-visible blocking of block-level elements.
-- It is useful to set various attributes/styles on the enclosed
-- elements
div ((DC attrs)::d) body = block_block tr mempty (body::d)
 where tr (DC body) = 
	    DC $ emit_elem "div" (Just attrs) (Just body) 

HSXML> :t div
div
  :: (MDoc d1, Build (DC CT_block d1) t,
      BuildR t  DC CT_block d1) =>
     DC CT_block d1 -> DC CT_block d1 -> t
HSXML> :t p
p :: (MDoc d, Build (DC CT_inline d) t,
      BuildR t  DC CT_block d) =>
     DC CT_inline d -> t 

ここまで見て来たように、HSXML ではブロック要素とインライン要素、属性はそれぞれ別の型です。ですが、上の例で使われている HSXML_ext モジュール title のように、あるタグを要素としても属性としても扱いたくなることもあります。同様に、ブロック要素としてもインライン要素としても扱えるようなタグが欲しくなることがあります。このような場合、型クラスを用いて多相的に振る舞うようにタグを定義します。

例えば、title タグは、以下のような「ブロック要素やインライン要素、属性、それぞれの型に対するインスタンスを定義した型クラス」を利用することで「ブロック要素としてもインライン要素としても属性としても扱える」ように定義されています。


-- Title can be either 
--    - a block-level element whose content is CT_inline
--    - an attribute (whose content is, therefore, CT_attr)
--    - an inline element, in which case it is the same as tspan
--      (however, "title ..." is more informative than "tspan ...")
class MkTitle ctx where
    type MkTitleI ctx :: *
    mk_title :: MDoc d => DC (MkTitleI ctx) d -> DC ctx d

instance MkTitle CT_block where
    type MkTitleI CT_block  = CT_inline
    mk_title (DC body) = 
         DC $ emit_elem "title" Nothing (Just body)

instance MkTitle CT_battr where
    type MkTitleI CT_battr  = CT_attr
    mk_title (DC body) = DC $ emit_attr "title" body

instance MkTitle CT_inline where
    type MkTitleI CT_inline  = CT_inline
    mk_title = id

title x = build mk_title dc_empty x 

同様に、Quotation モジュール (小文字の quote.hs) の quote タグも、ブロック要素(blockquote 要素)とインライン要素(q 要素)の二つの役割を持つように定義されています。


-- We define two different ways of rendering quote based on the context:
-- inline vs block.
-- The children of the quote are in the same context as the quote itself.
-- In addition, quote takes attributes, a (DC CT_block d) document

class MkQuote ctx where
    mk_quote :: MDoc d => DC CT_block d -> DC ctx d -> DC ctx d

instance MkQuote CT_inline where
    mk_quote (DC attrs) (DC body) = 
         cdata_sep . DC $ emit_elem "q" (Just attrs) (Just body)

instance MkQuote CT_block where
    mk_quote (DC attrs) (DC body) = 
         DC $ emit_elem "blockquote" (Just attrs) (Just body)

quote attrs body = build (mk_quote attrs) dc_empty body 

多相的に振る舞うタグを定義できることの他に、もう一つ忘れてはならないことがあります。それは(引数を取る) HSXML のタグが polyvariadic な(多相的かつ可変長な引数を取る)関数 であることです。例えば上の HSXML 文書の例にあったように、p 関数の引数として任意の数の文字列やタグを渡したり、attr 関数の引数として任意の数の属性を渡したりできます。

HSXML のタグの polyvariadic な関数としての振る舞いは、Monoid を拡張したクラスで定義されています。この辺をきちんと説明しようとするとそれだけで一つの記事になってしまうので、ここでは説明しません。とりあえず HSXML のタグが polyvariadic な関数であることと、そのために Monoid を拡張したクラスを使っていることが分かればそれで十分です。

HSXML での polyvariadic な関数の実現方法ついてどうしても気になる方は、自分でソースコードを見て調べてみてください。その場合、以下の記事などが参考になるかもしれません。

ここまでで、HSXML の基本的な使い方について説明しました。あとは HSXML モジュールや HSXML_ext モジュールで定義されているタグ、提供されているサンプル・プログラムなどを見て試してみて下さい。これらを見ていけば、どのように使うか大体分かると思います。(この記事の作成に使用したソースコードも以下に置いておきます: diary_12.hsShortMarkUp.hs

最後に、ここまで見てきたソースコードや GHCi のプロンプトの表示は、Template Haskell の準クォート(QuasiQuotes)を使って貼り付けています。Quote モジュール(大文字の Quote.hs)では、複数行に渡るテキストを簡単に貼り付けるための mup_text と mup_code という QuasiQuoter が用意されています。これらを使うことで、HSXML 中にソースコードやプロンプトの表示などをそのまま貼り付けることができます。


{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
〜 略 〜
      (p "href の定義は以下の通り。")
      [mup_code|
href :: MDoc d => URL -> DC CT_battr d
href url = DC $ emit_attr "href" (emit_url url)  |]

声なき言葉へ