実例によるPureScript

ウェブのための関数型プログラミング

Phil Freeman, "PureScript by Example - Functional Programming for the Web"

14 領域特化言語

14.1 この章の目標

この章では、多数の標準的な手法を使ったPureScriptにおける領域特化言語(domain-specific language, DSL) の実装について探求していきます。

領域特化言語とは、特定の問題領域での開発に適した言語のことです。領域特化言語の構文および機能は、その領域内の考え方を表現するコードの読みやすさを最大限に発揮すべく選択されます。本書の中では、すでに領域特化言語の例を幾つか見てきています。

この章では、領域特化言語の実装において、いくつかの標準的な手法による構造的なアプローチを取ります。これがこの話題の完全な説明だということでは決してありませんが、独自の目的に対する具体的なDSLを構築するには十分な知識を与えてくれるでしょう。

この章で実行している例は、HTML文書を作成するための領域特化言語になります。正しいHTML文書を記述するための型安全な言語を開発することが目的で、少しづつ実装を改善することによって作業していきます。

14.2 プロジェクトの準備

この章で使うプロジェクトには新しいBower依存性が追加されます。これから使う道具のひとつであるFreeモナドが定義されているpurescript-freeライブラリです。

このプロジェクトのソースコードは、Gruntを使ってビルドすることができます。

14.3 HTMLデータ型

このHTMLライブラリの最も基本的なバージョンは Data.DOM.Simpleモジュールで定義されています。このモジュールには次の型定義が含まれています。

newtype Element = Element
  { name         :: String
  , attribs      :: [Attribute]
  , content      :: Maybe [Content]
  }

data Content
  = TextContent String
  | ElementContent Element

newtype Attribute = Attribute
  { key          :: String
  , value        :: String
  }

Element型はHTMLの要素を表しており、各要素は要素名、属性のペア​​の配列と、要素の内容でで構成されています。contentプロパティでは、Maybeタイプを使って要素が開いている(他の要素やテキストを含む)か閉じているかを示しています。

このライブラリの鍵となる機能は次の関数です。

render :: Element -> String

この関数はHTML要素をHTML文字列として出力します。psci で明示的に適当な型の値を構築し、ライブラリのこのバージョンを試してみましょう。

> :i Data.DOM.Simple
> :i Data.Maybe

> render $ Element 
    { name: "p"
    , attribs: [
        Attribute 
          { key: "class"
          , value: "main" 
          }
      ]
    , content: Just [
        TextContent "Hello World!"
      ] 
    }
  
"<p class=\"main\">Hello World!</p>"

現状のライブラリにはいくつかの問題があります。

この章では、さまざまな手法を用いてこれらの問題を解決し、このライブラリーをHTML文書を作成するために使える領域特化言語にしていきます。

14.4 スマート構築子

最初に導入する手法は方法は単純なものですが、とても効果的です。モジュールの使用者にデータの表現を露出する代わりに、モジュールエクスポートリスト(module exports list)を使ってデータ構築子ElementContentAttributeを隠蔽し、正しいことが明らかなデータだけ構築する、いわゆるスマート構築子(smart constructors)だけをエクスポートします。

例を示しましょう。まず、HTML要素を作成するための便利な関数を提供します。

element :: String -> [Attribute] -> Maybe [Content] -> Element
element name attribs content = Element
  { name:      name
  , attribs:   attribs
  , content:   content
  }

次に、element関数を適用することによってHTML要素を作成する、スマート構築子を作成します。

a :: [Attribute] -> [Content] -> Element
a attribs content = element "a" attribs (Just content)

div :: [Attribute] -> [Content] -> Element
div attribs content = element "div" attribs (Just content)

p :: [Attribute] -> [Content] -> Element
p attribs content = element "p" attribs (Just content)

img :: [Attribute] -> Element
img attribs = element "img" attribs Nothing

最後に、正しいデータ構造だけを構築することがわかっているこれらの関数をエクスポートするように、モジュールエクスポートリストを更新します。

module Data.DOM.Smart
  ( Element()
  , Attribute(..)
  , Content(..)

  , a
  , div
  , p
  , img

  , render
  ) where

モジュールエクスポートリストはモジュール名の直後の括弧内に書きます。各モジュールのエクスポートは次の3種類のいずれかです。

ここでは、 Elementをエクスポートしていますが、データ構築子はエクスポートしていません。もしデータ構築子をエクスポートすると、モジュールの使用者が不正なHTML要素を構築できてしまいます。

AttributeContent型についてはデータ構築子をすべてエクスポートしています(エクスポートリストの記号..で示されています)。これから、これらの型にスマート構築子の手法を適用していきます。

すでにライブラリにいくつかの大きな改良を加わっていることに注意してください。

Content型にもとても簡単にこの手法を適用することができます。単にエクスポートリストからContent型のデータ構築子を取り除き、次のスマート構築子を提供します。

text :: String -> Content
text = TextContent

elem :: Element -> Content
elem = ElementContent

Attribute型にも同じ手法を適用してみましょう。まず、属性のための汎用のスマート構築子を用意します。最初の試みとしては、次のようなものになるかもしれません。

(:=) :: String -> String -> Attribute
(:=) key value = Attribute
  { key: key
  , value: value
  }

この定義では元のElement型と同じ問題に悩まされています。存在しなかったり、名前が間違っているような属性を表現することが可能です。この問題を解決するために、属性名を表すnewtypeを作成します。

newtype AttributeKey = AttributeKey String

それから、この演算子を次のように変更します。

(:=) :: AttributeKey -> String -> Attribute
(:=) (AttributeKey key) value = Attribute
  { key: key
  , value: value
  }

AttributeKeyデータ構築子をエクスポートしなければ、明示的にエクスポートされた次のような関数を使う以外に、使用者が型AttributeKeyの値を構築する方法はありません。いくつかの例を示します。

href :: AttributeKey
href = AttributeKey "href"

_class :: AttributeKey
_class = AttributeKey "class"

src :: AttributeKey
src = AttributeKey "src"

width :: AttributeKey
width = AttributeKey "width"

height :: AttributeKey
height = AttributeKey "height"

新しいモジュールの最終的なエクスポートリストは次のようになります。もうどんなデータ構築子も直接エクスポートしていないことに注意してください。

module Data.DOM.Smart
  ( Element()
  , Attribute()
  , Content()
  , AttributeKey()

  , a
  , div
  , p
  , img

  , href
  , _class
  , src
  , width
  , height

  , (:=)
  , text
  , elem

  , render
  ) where

psci でこの新しいモジュールを試してみると、コードが大幅に簡潔になり、改良されていることがわかります。

> :i Data.DOM.Smart
> render $ p [ _class := "main" ] [ text "Hello World!" ]
  
"<p class=\"main\">Hello World!</p>"

しかし、基礎のデータ表現が変更されていないので、render関数を変更する必要はなかったことにも注目してください。これはスマート構築子による手法の利点のひとつです。外部APIの使用者によって認識される表現から、モジュールの内部データ表現を分離することができるのです。

演習

  1. (簡単) Data.DOM.Smartモジュールでrenderを使った新しいHTML文書の作成を試してみましょう。

  2. (やや難しい) checkeddisabledなど、値を要求しないHTML属性がありますが、これらは次のような空の属性として表示されるかもしれません。

    <input disabled>

    空の属性を扱えるようにAttributeの表現を変更してください。要素に空の属性を追加するために、 :(=)の代わりに使える関数を記述してください。

14.5 幻影型

次に適用する手法についての動機を与えるために、次のコードを考えてみます。

> :i Data.DOM.Phantom
> render $ img [ src    := "cat.jpg"
               , width  := "foo"
               , height := "bar" 
               ]
  
"<img src=\"cat.jpg\" width=\"foo\" height=\"bar\" />"

ここでの問題は、widthheightについての文字列値を提供しているということで、ここで与えることができるのはピクセルやパーセントの単位の数値だけであるべきです。

AttributeKey型にいわゆる幻影型(phantom type)引数を導入すると、この問題を解決できます。

newtype AttributeKey a = AttributeKey String

定義の右辺に対応する型aの値が存在しないので、この型変数a幻影型と呼ばれています。この型aはコンパイル時により多くの情報を提供するためだけに存在しています。任意の型AttributeKey aの値は実行時には単なる文字列ですが、そのキーに関連付けられた値に期待されている型を教えてくれます。

AttributeKeyの新しい形式で受け取るように、(:=)演算子の型を次のように変更します。

(:=) :: forall a. (IsValue a) => AttributeKey a -> a -> Attribute
(:=) (AttributeKey key) value = Attribute
  { key: key
  , value: toValue value
  }

ここで、ファントム型引数 aは、属性キーと属性値が互換性のある型を持っていることを確認するために使われます。使用者はAttributeKey aを型の値を直接作成できないので(ライブラリで提供されている定数を介してのみ得ることができます)、すべての属性が正しくなります。

IsValue制約は、キーに関連付けられた値がなんであれ、その値を文字列に変換し、生成したHTML内に出力できることを保証します。IsValue型クラスは次のように定義されています。  

class IsValue a where
  toValue :: a -> String

StringNumber型についての型クラスインスタンスも提供しておきます。

instance stringIsValue :: IsValue String where
  toValue = id

instance numberIsValue :: IsValue Number where
  toValue = show

また、これらの型が新しい型変数を反映するように、AttributeKey定数を更新しなければいけません。

href :: AttributeKey String
href = AttributeKey "href"

_class :: AttributeKey String
_class = AttributeKey "class"

src :: AttributeKey String
src = AttributeKey "src"

width :: AttributeKey Number
width = AttributeKey "width"

height :: AttributeKey Number
height = AttributeKey "height"

これで、不正なHTML文書を表現することが不可能で、widthheight属性を表現するのに数を使うことが強制されていることがわかります。

> :i Data.DOM.Phantom
> render $ img [ src    := "cat.jpg"
               , width  := 100
               , height := 200 
               ]
  
"<img src=\"cat.jpg\" width=\"100\" height=\"200\" />"

演習 

  1. (簡単) ピクセルまたはパーセントの長さのいずれかを表すデータ型を作成してください。その型について IsValueのインスタンスを書いてください。この型を使うようにwidthheight属性を変更してください。

  2. (難しい) ファントム型を使って真偽値truefalseについての表現を最上位で定義することで、AttributeKeydisabledchackedのような空の属性を表現しているかどうかを符号化することができます。、

    data True
    data False

    ファントム型を使って、使用者が(:=)演算子を空の属性に対して使うことを防ぐように、前の演習の解答を変更してください。

14.6 Freeモナド

APIに施す最後の変更は、Content型をモナドにしてdo記法を使えるようにするために、Freeモナドと呼ばれる構造を使うことです。Freeモナドは、入れ子になった要素をわかりやすくなるよう、HTML文書の構造化を可能にします。次のようなコードを考えます。

p [ _class := "main" ]
  [ elem $ img 
      [ src    := "cat.jpg"
      , width  := 100
      , height := 200 
      ]
  , text "A cat"
  ]

これを次のように書くことができるようになります。

p [ _class := "main" ] $ do
  elem $ img 
    [ src    := "cat.jpg"
    , width  := 100
    , height := 200 
    ]
  text "A cat"

しかし、do記法だけがFreeモナドの恩恵だというわけではありません。モナドのアクションの表現をその解釈から分離し、同じアクションに複数の解釈を持たせることをFreeモナドは可能にします。

Freeモナドはpurescript-freeライブラリのControl.Monad.Freeモジュールで定義されています。psciを使うと、次のようにFreeモナドについての基本的な情報を見ることができます。

> :i Control.Monad.Free
> :k Free
(* -> *) -> * -> *

Freeの種は、引数として型構築子を取り、別の型構築子を返すことを示しています。実は、Freeモナドは任意のFunctorMonadにするために使うことができます!

モナドのアクションの表現を定義することから始めます。これを行うには、サポートする各モナドアクションそれぞれについて、ひとつのデータ構築子を持つFunctorを作成する必要があります。今回の場合、2つのモナドのアクションはelemtextになります。実際には、Content型を次のように変更するだけです。

data ContentF a
  = TextContent String a
  | ElementContent Element a

instance functorContentF :: Functor ContentF where
  (<$>) f (TextContent s a) = TextContent s (f a)
  (<$>) f (ElementContent e a) = ElementContent e (f a)

ここで、このContentF型構築子は以前のContentデータ型とよく似ています。Functorインスタンスでは、単に各データ構築子で型aの構成要素に関数fを適用します。

これにより、最初の型引数としてContentF型構築子を使うことで構築された、新しいContent型構築子をFreeモナドを包むnewtypeとして定義することができます。

newtype Content a = Content (Free ContentF a)

ここでnewtypeを使っているのは、使用者に対してライブラリの内部表現を露出することを避けるためです。Contentデータ構築子を隠すことで、提供しているモナドのアクションだけを使うことを仕様者に制限しています。

ContentFFunctorなので、Free ContentFに対するMonadインスタンスが自動的に手に入り、このインスタンスをContent上のMonadインスタンスへと持ち上げることができます。

runContent :: forall a. Content a -> Free ContentF a
runContent (Content x) = x

instance functorContent :: Functor Content where
  (<$>) f (Content x) = Content (f <$> x)

instance applyContent :: Apply Content where
  (<*>) (Content f) (Content x) = Content (f <*> x)

instance applicativeContent :: Applicative Content where
  pure = Content <<< pure

instance bindContent :: Bind Content where
  (>>=) (Content x) f = Content (x >>= (runContent <<< f))

instance monadContent :: Monad Content

Contentの新しい型引数を考慮するように、少しElementデータ型を変更する必要があります。モナドの計算の戻り値の型がUnitであることだけが要求されます。

newtype Element = Element
  { name         :: String
  , attribs      :: [Attribute]
  , content      :: Maybe (Content Unit)
  }

また、 Contentモナドについての新しいモナドのアクションになるelemtext関数を変更する必要があります。これを行うには、 Control.Monad.Freeモジュールで提供されているliftF関数を使います。この関数の(簡略化された)型は次のようになっています。

liftF :: forall f a. (Functor f) => f a -> Free f a

liftFは、何らかの型aについて、型f aの値からFreeモナドのアクションを構築できるようにします。今回の場合、ContentF型構築子のデータ構築子を次のようにそのまま使うだけです。

text :: String -> Content Unit
text s = Content $ liftF $ TextContent s unit

elem :: Element -> Content Unit
elem e = Content $ liftF $ ElementContent e unit

他にもコードの変更はありますが、興味深い変更はrender関数に対してのものです。ここでは、このFreeモナドを解釈しなければいけません。

14.7 モナドの解釈

Control.Monad.Freeモジュールでは、Freeモナドで計算を解釈するための多数の関数が提供されています。

go :: forall f a. (Functor f) => 
  (f (Free f a) -> Free f a) -> 
  Free f a -> 
  a
  
goM :: forall f m a. (Functor f, Monad m) => 
  (f (Free f a) -> m (Free f a)) -> 
  Free f a -> 
  m a
  
iterM :: forall f m a. (Functor f, Monad m) => 
  (forall a. f (m a) -> m a) -> 
  Free f a -> 
  m a

純粋な結果を計算するためにFreeモナドを使いたいなら、 go関数が便利です。goM関数とiterM関数は、モナドを使用してFreeモナドのアクションを解釈することができます。この2つの関数は解釈関数の型が若干異なりますが、ここではiterM関数を使います。興味のある読者は、代わりにgoM関数を使用してこのコードを再実装してみるといいでしょう。

まず、アクションを解釈することができるモナドを選ばなければなりません。Writer Stringモナドを使って、結果のHTML文字列を累積することにします。

新しいrenderメソッドは補助関数renderElementに移譲して開始し、Writerモナドで計算を実行するためexecWriterを使用します。

render :: Element -> String
render e = execWriter $ renderElement e

renderElementはwhereブロックで定義されています。

  where
  renderElement :: Element -> Writer String Unit
  renderElement (Element e) = do

renderElement の定義は簡単で、いくつかの小さな文字列を累積するためにWriterモナドのtellアクションを使っています。

    tell "<"
    tell e.name
    for_ e.attribs $ \a -> do
      tell " "
      renderAttribute a
    renderContent e.content

次に、同じように簡単なrenderAttribute関数を定義します。

    where
    renderAttribute :: Attribute -> Writer String Unit
    renderAttribute (Attribute a) = do
      tell a.key
      tell "=\""
      tell a.value
      tell "\""

renderContent関数は、もっと興味深いものです。ここでは、iterM関数を使って、Freeモナドの内部で補助関数renderContentItemに移譲する計算を解釈しています。

    renderContent :: Maybe (Content Unit) -> Writer String Unit
    renderContent Nothing = tell " />"
    renderContent (Just (Content content)) = do
      tell ">"
      iterM renderContentItem content
      tell "</"
      tell e.name
      tell ">"

renderContentItemの型はiterMの型シグネチャから推測することができます。関手fは型構築子ContentFで、モナドmは解釈している計算のモナド、つまりWriter Stringです。これによりrenderContentItem について次の型シグネチャがわかります。

    renderContentItem :: forall a. ContentF (Writer String a) -> Writer String a

ContentFの二つのデータ構築子でパターン照合するだけで、この関数を実装することができます。

    renderContentItem (TextContent s rest) = do
      tell s
      rest
    renderContentItem (ElementContent e rest) = do
      renderElement e
      rest

それぞれの場合において、式restは型Writer Stringを持っており、解釈計算の残りを表しています。restアクションを呼び出すことによって、それぞれの場合を完了することができます。

これで完了です!psciで、次のように新しいモナドのAPIを試してみましょう。

> :i Data.DOM.Free
> render $ p [] $ do
    elem $ img [ src := "cat.jpg" ]
    text "A cat"
  
"<p><img src=\"cat.jpg\" />A cat</p>"

演習

  1. (やや難しい) ContentF型に新しいデータ構築子を追加して、生成されたHTMLにコメントを出力する新しいアクションcommentに対応してください。liftFを使ってこの新しいアクションを実装してください。新しい構築子を適切に解釈するように、解釈renderContentItemを更新してください。

  2. (難しい) goMiterM関数の問題のひとつに、スタック安全でないというものがあります。大きいモナドのアクションは、解釈したときにスタックオーバーフローを引き起こす可能性があるのです。しかしながら、Control.Monad.Freeライブラリは、スタック安全な gogoEff関数を提供しています。Writerモナドの代わりにST作用を利用して、goEff関数を使ってContentモナドを解釈してください。

14.8 言語の拡張

すべてのアクションが型Unitの何かを返すようなモナドは、さほど興味深いものではありません。実際のところ、概ね良くなったと思われる構文は別として、このモナドはMonoid以上の機能は何の追加していません。

意味のある結果を返す新しいモナドアクションでこの言語を拡張することで、Freeモナド構造の威力を説明しましょう​​。

アンカーを使用して文書のさまざまな節へのハイパーリンクが含まれているHTML文書を生成するとします。手作業でアンカーの名前を生成すればいいので、これは既に実現できています。文書中で少なくとも2回、ひとつはアンカーの定義自身に、もうひとつはハイパーリンクに、アンカーが含まれています。しかし、この方法には根本的な問題がいくつかあります。

自分の間違いから開発者を保護するために、アンカー名を表す新しい型を導入し、新しい一意な名前を生成するためのモナドアクションを提供することができます。

最初の手順は、名前の型を新しく追加することです。

newtype Name = Name String

runName :: Name -> String
runName (Name n) = n

繰り返しになりますが、NameStringのnewtypeとして定義しており、モジュールのエクスポートリスト内でデータ構築子をエクスポートしないように注意する必要があります。

次に、属性値としてNameを使うことができるように、新しい型IsValue型クラスのインスタンスを定義します。

instance nameIsValue :: IsValue Name where
  toValue (Name n) = n

また、次のようにa要素に現れるハイパーリンクの新しいデータ型を定義します。

data Href
  = URLHref String
  | AnchorHref Name

instance hrefIsValue :: IsValue Href where
  toValue (URLHref url) = url
  toValue (AnchorHref (Name nm)) = "#" ++ nm

href属性の型の値を変更して、この新しいHref型の使用を強制します。また、要素をアンカーに変換するのに使う新しいname属性を作成します。

href :: AttributeKey Href
href = AttributeKey "href"

name :: AttributeKey Name
name = AttributeKey "name"

残りの問題は、現在モジュールの使用者が新しい名前を生成する方法がないということです。Contentモナドでこの機能を提供することができます。まず、ContentF型構築子に新しいデータ構築子を追加する必要があります。

data ContentF a
  = TextContent String a
  | ElementContent Element a
  | NewName (Name -> a)

NewNameデータ構築子は型Nameの値を返すアクションに対応しています。データ構築子の引数としてNameを要求するのではなく、型Name -> a関数を提供するように使用者に要求していることに注意してください。型a計算の残りを表していることを思い出すと、この関数は、型Nameの値が返されたあとで、計算を継続する方法を提供するというように直感的に理解することができます。

新しいデータ構築子を考慮するように、ContentFについてのFunctorインスタンスを更新する必要があります。

instance functorContentF :: Functor ContentF where
  (<$>) f (TextContent s a) = TextContent s (f a)
  (<$>) f (ElementContent e a) = ElementContent e (f a)
  (<$>) f (NewName k) = NewName (f <<< k)

そして、先ほど述べたように、liftF関数を使うと新しいアクションを構築することができます。

newName :: Content Name
newName = Content $ liftF $ NewName id

id関数を継続として提供していることに注意してください。型Nameの結果を変更せずに返すということを意味しています。

最後に、新しいアクションを解釈するために、解釈関数を更新する必要があります。以前は計算を解釈するためにWriter Stringモナドを使っていましたが、このモナドは新しい名前を生成する能力を持っていないので、何か他のものに切り替えなければなりません。RWSモナドなら、Writerの機能を提供するだけでなく、純粋な状態を扱うことができます。型シグネチャを短く保てるように、この解釈モナドを型同義語としての定義しておきます。

type Interp = RWS Unit String Number

RWSモナドは3つの型引数を取ることを思い出してください。 最初は大域的な設定で、今回は単なるUnitです。2つめは「ログ」型で、累積するH TML文字列です。最後の引数は状態の型で、この場合は増加していくカウンタとして振る舞う数で、一意な名前を生成するのに使われます。

WriterRWSモナドはそれらのアクションを抽象化するのに同じ型クラスメンバを使うので、どのアクションも変更する必要がありません。必要なのは、Writer Stringへの参照すべてをInterpで置き換えることだけです。しかし、この計算を実行するために使われるハンドラを変更しなければいけません。execWriterの代わりに、evalRWSを使います。

render :: Element -> String
render e = snd $ evalRWS (renderElement e) unit 0

sndの呼び出しはevalRWSから返されたTuple2番めの要素だけを返すようにします。この場合は、累積されたHTML文字列を表しています。

新しいNewNameデータ構築子を解釈するために、renderContentItemに新しい場合分けを追加しなければいけません。

    renderContentItem (NewName k) = do
      n <- get
      let name = Name $ "name" ++ show n
      put $ n + 1
      k name

ここで、型Name -> Interp aの継続kが与えられているので、型Interp aの解釈を構築しなければいけません。この解釈は単純です。getを使って状態を読み、その状態を使って一意な名前を生成し、それからputで状態をインクリメントしています。最後に、継続にこの新しい名前を渡して、計算を完了します。

これにより、psciで、Contentモナドの内部で一意な名前を生成し、要素の名前とハイパーリンクのリンク先の両方を使って、この新しい機能を試してみましょう。

> :i Data.DOM.Name
> render $ p [ ] $ do
    top <- newName
    elem $ a [ name := top ] $ 
      text "Top"
    elem $ a [ href := AnchorHref top1 ] $ 
      text "Back to top"
  
"<p><a name=\"name0\">Top</a><a href=\"#name0\">Back to top</a></p>"

複数回のnewName呼び出しの結果が、実際に一意な名前になっていることを確かめてみてください。

演習

  1. (やや難しい) 使用者からElement型を隠蔽すると、さらにAPIを簡素化することができます。次の手順に従って、これらの変更を行ってください。

    • pimgのような(返り値がElementの)関数をelemアクションと結合して、型Content Unitを返す新しいアクションを作ってください。
    • Content aの引数を許容し、結果の型Tuple Stringを返すように、render関数を変更してください。
  2. (難しい) 次の新しいアクションをサポートするように、ContentFタイプを変更してください。

    isMobile :: Content Boolean

  このアクションは、この文書がモバイルデバイス上での表示のためにレンダリングされているかどうかを示す真偽値を返します。  
  ヒントRWSモナドのaskアクションと Readerコンポーネントを使って、このアクションを解釈してください。

14.9 まとめ

この章では、いくつかの標準的な技術を使って、単純な実装を段階的に改善することにより、HTML文書を作成するための領域特化言語を開発しました。

使用者が間違いを犯すのを防ぎ、領域特化言語の構文を改良するために、これらの手法はすべてPureScriptのモジュールと型システムを活用しています。

関数型プログラミング言語による領域特化言語の実装は活発に研究されている分野ですが、いくつかの簡単なテクニックに対して役に立つ導入を提供し、表現力豊かな型を持つ言語で作業すること威力を示すことができていれば幸いです。