実体化
実体化(具体化)は、プログラマがコンパイラシンボルテーブルから情報を取得できるテンプレートHaskellツールです。 モナド関数
reify
∷ Name → Q Info
は、この名前に関する
∷ Name → Q Info
返します:グローバル識別子(関数、定数、コンストラクター)の場合-タイプを取得します。タイプまたはクラスの場合-構造を取得します。
Info
型の定義は、
Language.Haskell.TH.Syntax
モジュールにあります。
実体化を使用して型構造を取得できますが、この方法では関数本体を取得することはできません。 関数の本体を具体化する必要がある場合は、関数の定義を引用する必要があり、別のテンプレートを使用してこの定義を引き続き使用できます。 たとえば、次のように:
$(optimize [d| fib = … |])
かそこら
fib = $(optimize [| … |])
実際、元の記事では具体化については何も述べていません。 このトピックがどれほど重要かはわかりません。必要な最低限の知識は、
reify
関数と
Info
タイプに限定されますが、名前に関する情報を取得できないという事実に関連する微妙な点がいくつかあります。 このトピックが興味深い場合は、いくつかの情報を収集し、それに関する別のメモを書く(またはここに貼り付ける)ことができます。
Liteの引用名
目的の識別子に対応する名前(
∷ Name
)を取得するには、
mkName
関数を使用できますが、これは安全な解決策ではありません
mkName
は
mkName
名前を返すため、コンテキストによって解釈が異なる場合があります。 そして、ここにコード
VarE id ← [| foo |]
VarE id ← [| foo |]
はこの意味で安全です。引用は名前を修飾するため(
My.Own.Module.foo
ような
My.Own.Module.foo
)ですが、このコードは冗長すぎるため、使用するにはモナドコンテキストが必要です。 幸いなことに、テンプレートHaskellには名前を引用する別の簡単な形式があります:
'foo
(
'foo
前の単一引用符)は
Name
型であり、識別子
foo
対応する修飾
Name
を含むため
let id = 'foo
コード
VarE id ← [| foo |]
VarE id ← [| foo |]
。 このコンストラクトは単純な
Name
タイプ(
Q Exp
または
Q Name
ではない)を持っているので、モナドが不可能な場合に使用できることに注意してください。例えば:
f ∷ Exp → Exp f (App (Var m) e) | m == 'map = …
それにもかかわらず、この新しい形式は引用であり、引用符括弧と同じルールに従います
[| … |]
[| … |]
。 たとえば、これらの角かっこ内では使用できません(不可能:
[| 'foo |]
)。 ただし、接着にはタイプ
Q …
必要なため、接着を適用できません(不可能:
$( 'foo )
)。
Q …
さらに重要なことは、この形式は静的に定義され、明確な解釈で完全修飾名を返します。
Haskellの名前空間は、物事を少し複雑にします。 引用
[| P |]
[| P |]
は
[| P |]
のデータコンストラクターを意味し、
[t| P |]
[t| P |]
は、タイプ
P
コンストラクターを意味します
P
したがって、「円滑化された引用」には、これらのエンティティを分離する同じ方法が必要です。 型コンテキストでは、2つの単一引用符のみが使用されます。
-
'Foo
は、「式のコンテキストでのFoo
データコンストラクター」を意味します -
'foo
は「式のコンテキストでの名前foo
」を意味します -
''Foo
は「型のコンテキストでの型Foo
コンストラクター」を意味します -
''foo
は、「型のコンテキストにおけるfoo
型の変数」を意味します
Show
クラスのインカネーションを生成する例では、軽量の引用形式が使用されます。
エラーメッセージと回復
引用モナドを使用すると、エラーメッセージを送信して回復できます。
report ∷ Bool → String → Q ()
report
関数は、2番目の引数で指定されたメッセージを表示します。 最初の引数が
True
場合、結果はエラーとして認識されます。それ以外の場合、メッセージは単に表示されます( 警告として )。 いずれにせよ、計算を停止する必要がある場合は、モナド
fail
使用して計算を続行
fail
ます。 「閉じる」
recover
がない場合、コンパイルは失敗します。
recover ∷ Q a → Q a → Q a
recover ab
を
recover ab
する呼び出しは
b
開始します。
b
が
report True "…"
呼び出しを
report True "…"
ていた場合、
b
。
b
がそのようなエラーなしで終了した場合、その結果が返され、
a
無視されます。
location ∷ Q Loc
現在の接着が行われているコード内の場所の座標を(
Loc
構造の形式で)返します-エラーメッセージに便利です。
デバッグ
THプログラムのデバッグを簡素化するために、GHCは特別なフラグ
-ddump-splices
サポートしています。これにより、モジュールのロード中にすべての最上位テンプレートを貼り付けた結果が表示されます。
さらに、インタープリターでは、関数
runQ
∷ Q a → IO a
を使用してモナド
Q
計算を実行し、抽象構文(AST)の形式または対応するHaskellコードの形式で結果を表示できます。
$ ghci –XTemplateHaskell … Prelude> :m + Language.Haskell.TH Prelude Language.Haskell.TH> runQ [| \x _ -> x |] >>= print LamE [VarP x_1,WildP] (VarE x_1) Prelude Language.Haskell.TH> runQ [| \x _ -> x |] >>= putStrLn . pprint \x_0 _ -> x_0
pprint
関数は、コンパイル時にプログラムに貼り付けられるHaskellコードを表示します。 さらなる例として、引数を無視して与えられた文字列を返すラムダ式を生成する簡単なテンプレートを書きます:
module Cnst where import Language.Haskell.TH cnst :: Int -> String -> Q Exp cnst ns = return (LamE (replicate n WildP) (LitE (StringL s)))
これでインタープリターでテストできます:
$ ghci -XTemplateHaskell Cnst.hs … *Cnst> runQ (cnst 2 "str") >>= print LamE [WildP,WildP] (LitE (StringL "str")) *Cnst> runQ (cnst 2 "str") >>= putStrLn . pprint \_ _ -> "str"
テンプレートをインポートするモジュールでも同じことができます。
{-# LANGUAGE TemplateHaskell #-} module Main where import Language.Haskell.TH import Cnst -- cnst : cnst1 :: t -> [Char] cnst1 = $(cnst 1 "x") cnst2 :: t1 -> t2 -> [Char] cnst2 = $(cnst 2 "str") -- , 20 cnst20 = $(cnst 20 "foo") -- , runQ: main = do runQ(cnst 1 "x") >>= print runQ(cnst 2 "str") >>= print runQ(cnst 20 "foo") >>= print runQ(cnst 1 "x") >>= putStrLn.pprint runQ(cnst 2 "str") >>= putStrLn.pprint runQ(cnst 20 "foo") >>= putStrLn.pprint
例:derivedShow
これは、テンプレートHaskellを使用してクラスのインスタンスを自動的に生成する方法を示す小さな例です。 ASTコンストラクター、軽量引用、引用ブラケット、実体化を使用します。一般に、この記事で説明したほぼすべてのものを使用します。 簡単にするために、テンプレートは単純な代数型(パラメーター化なし、名前付きフィールドなしなど)で
showClause
機能します
showClause
関数を個別に含めて、元のコードに(読みやすさを向上させるために)いくつかの変更を加えました。
- Main.hs
{-# LANGUAGE TemplateHaskell #-} module Main where import Derive data T = A Int String | B Integer | C $(deriveShow ''T) main = print [A 1 "s", B 2, C] -- [A 1 "s",B 2,C]
- Derive.hs
{-# LANGUAGE TemplateHaskell #-} module Derive where import Language.Haskell.TH import Control.Monad data T1 = T1 -- , -- n genPE :: Int -> Q ([PatQ], [ExpQ]) genPE n = do ids <- replicateM n (newName "x") return (map varP ids, map varE ids) -- show : -- show (A x1 x2) = "A "++show x1++" "++show x2 showClause :: Con -> Q Clause showClause (NormalC name fields) = do -- , .. "A". nameBase let constructorName = nameBase name -- (pats,vars) <- genPE (length fields) -- (" "++show x1++...++"") [x1, ...] let f [] = [| constructorName |] f (v:vars) = [| " " ++ show $v ++ $(f vars) |] -- clause [conP name pats] -- (A x1 x2) (normalB (f vars)) [] -- "A"++" "++show x1++" "++show x2 -- , Show deriveShow :: Name -> Q [Dec] deriveShow t = do -- t TyConI (DataD _ _ _ constructors _) <- reify t -- show: -- show (A x1 x2) = "A "++show x1++" "++show x2 -- show (B x1) = "B "++show x1 -- show C = "C" showbody <- mapM showClause constructors -- -- (T1) (x = "text") showbody d <- [d| instance Show T1 where show x = "text" |] let [InstanceD [] (AppT showt (ConT _T1)) [FunD showf _text]] = d return [InstanceD [] (AppT showt (ConT t )) [FunD showf showbody]]
おわりに
この記事はここで終わり、Haskellでメタプログラミングを使い始めるのに十分な量の主要なトピックをカバーしたので、テンプレートHaskellの紹介が行われたと想定できます。 このトピックについてさらに詳しく知るには、THの公式ページをご覧になることをお勧めします。ここでは、他の記事や多くの例へのリンクを見つけることができます。