このテキストは、Bulat Ziganshinによって書かれたTemplate Haskellドキュメントの翻訳です。 テキスト全体の翻訳は、知覚を容易にするためにいくつかの論理部分に分割されます。 テキスト内のその他の斜体は、翻訳者のメモです。
テンプレートHaskell (以下TH)は、メタプログラミング用のHaskell拡張機能です。 これにより、コンパイル段階でプログラムのアルゴリズム構築が可能になります。 これにより、開発者は、マクロのような拡張機能、ユーザー主導の最適化(インライン化など)、汎用プログラミング(ポリタイププログラミング)、既存のデータ構造や関数の生成など、Haskell自体では利用できないさまざまなプログラミング手法を使用できます。 たとえば、コード
yell file line = fail ($(printf "Error in file %s line %d") file line)
THを使用して変換できます
yell file line = fail ((\x1 x2 -> "Error in file "++x1++" line "++show x2) file line)
別のサンプルコード
data T = A Int String | B Integer | C $(deriveShow ''T)
に変換できます
data T = A Int String | B Integer | C instance Show T show (A x1 x2) = "A "++show x1++" "++show x2 show (B x1) = "B "++show x1 show C = "C"
THでは、Haskellコードは通常のHaskell関数(わかりやすくするためにテンプレートと呼びます)によって生成されます。 THを使用するために知っておく必要がある最低限の事項は、次のトピックです。
- テンプレートでのHaskellコードの表現方法(TH関数)
- 引用モナドを使用して名前を統一する方法
- 生成されたTHコードがプログラムに挿入される方法
Haskellコードがテンプレートでどのように表示されるか
テンプレートHaskellでは、Haskellコードのフラグメントは、従来の代数データ型を使用して表されます 。 これらの型はHaskell構文に従って構築され、特定のコードの抽象構文ツリー (AST-抽象構文ツリー)を表します。 式を表す
Exp
型、パターンの
Pat
、リテラルの
Lit
、宣言の
Dec
、
Type
などがあります。 これらすべてのタイプの定義は、
Language.Haskell.TH.Syntax
モジュールのドキュメントに記載されています。 これらはHaskell構文のルールに従って相互接続されているため、Haskellコードのフラグメントを表す値を構築できます。 以下に簡単な例を示します。
varx = VarE (mkName "x")
x
表します。 単純変数「x
」patx = VarP (mkName "x")
x
表します。 サンプルで使用されているのと同じ変数「x
」str = LitE (StringL "str")
"str"
表し"str"
tuple = TupE [varx, str]
(x,"str")
LamE [patx] tuple
\x -> (x,"str")
Exp
型のすべてのコンストラクターの名前は
E
で終わり、
Pat
型のコンストラクターの名前は
P
で終わります。 上記で使用された
mkName
関数は、通常の文字列(
String
)から
Name
型(識別子を表す)の値を、その内容を名前として作成します。
そのため、Haskellコードを作成するために、TH関数は、このコードの表現である
Exp
型(
Dec
、
Pat
または
Type
使用することもできます)の値を単純に構築して返す必要があります。 実際、必要なHaskellコードを提示する方法を知るためにこれらのタイプのデバイスを徹底的に研究する必要はありません-デバッグセクションでは、Haskellコードの特定のフラグメントのTH表現を取得する方法を説明します。
引用モナドを使用して名前を統一する方法
ただし、テンプレートは
Exp
型の単純な値を返す純粋な関数ではありません。 代わりに、モナド関数
newName
:: String -> Q Name
newName
:: String -> Q Name
を使用して変数の一意の名前を自動的に生成できる特別な
Q
モナド(「qoutationモナド」と呼ばれる)での計算です。 呼び出されるたびに、このプレフィックスで新しい一意の名前が生成されます。 この名前は、パターンの一部(
VarP
:: Name -> Pat
VarP
:: Name -> Pat
コンストラクターを使用)または式(
VarE
:: Name -> Exp
VarE
:: Name -> Exp
)として使用できます。
簡単な例を書いてみましょう
tupleReplicate
テンプレートは、次のように使用すると、
“$(tupleReplicate n) x"
、すべての位置に要素
x
を持つnリストのタプルを返します(リストの
replicate
関数に似ています)。テンプレートの引数であり、
x
は生成された匿名関数(ラムダ式)の引数です。このテンプレートの定義を含むモジュールのコードを指定します(
Language.Haskell.TH
モジュールは、THの操作に必要なすべてのツールを提供します)。
module TupleReplicate where import Language.Haskell.TH tupleReplicate :: Int -> Q Exp tupleReplicate n = do id <- newName "x" return $ LamE [VarP id] (TupE $ replicate n $ VarE id)
たとえば、「
tupleReplicate 3
」を呼び出すと、Haskell式「
(\x -> (x,x,x))
」に相当する
Exp
値が返されます。
生成されたTHコードがプログラムに挿入される方法
貼り付け(スプライス)は、「
$x
」の形式(
x
は識別子)または「
$(...)
」の形式
$(...)
省略記号は対応する式を意味する)で記述されます。
$
文字と識別子または括弧の間にスペースがないことが重要です。 この
$
使用は、修飾名
Mx
が関数構成演算子の値をオーバーライドするのと同様に、この文字の値を中置演算子としてオーバーライドします
.
」 演算子が必要な場合は、文字をスペースで囲む必要があります。
貼り付けが表示される場合があります
- 式; 貼り付けられた式は
Q Exp
タイプでなければなりません。 - トップレベル広告 貼り付けられた式は
Q [Dec]
型でなければなりません。 貼り付けによって生成された広告は、以前のコードで宣言された識別子にのみアクセスできます(これは、発表の順序が重要ではない通常のHaskellプログラムでは一般的ではありません)。 - タイプ; 貼り付けられた式は
Q Type
型でなければなりません。
また、それを知っている必要があります
- GHCを起動するときに、
-XTemplateHakell
フラグを使用して特別なTH構文を有効にします。 または、ソースに{-# LANGUAGE TemplateHaskell #-}
ディレクティブを含めることができます。 - テンプレートは外部からのみ使用できます。 つまり、1つのモジュールでテンプレートを定義してすぐに使用することはできません(貼り付け)。 (これは、現時点ではテンプレートがまだコンパイルされていないためです)
tupleReplicate
テンプレートを使用するモジュールの例:
{-# LANGUAGE TemplateHaskell #-} module Test where import TupleReplicate main = do print ($(tupleReplicate 2) 1) -- (1,1) print ($(tupleReplicate 5) "x") -- ("x","x","x","x","x")
続く
次のセクションでは、より興味深く高度なトピックを取り上げます。
- 引用モナド
- 引用符ブラケット
- 実体化(具体化)
- エラーメッセージと回復
- デバッグ
printf
と
deriveShow
例は
deriveShow
ます。
PSこれは私の最初の翻訳なので、記事のトピックに関する建設的な批判と実質的な議論を期待しています。
更新:
パート2.コード引用ツール
パート3. THのその他の側面