テンプレートHaskellの紹介。 パート3. THのその他の側面

このテキストは、Bulat Ziganshinによって書かれたTemplate Haskellドキュメントの翻訳です。 テキスト全体の翻訳は、知覚を容易にするためにいくつかの論理部分に分割されます。 テキスト内のその他の斜体は、翻訳者のメモです。 前のパーツ:





実体化



実体化(具体化)は、プログラマがコンパイラシンボルテーブルから情報を取得できるテンプレート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 ∷ ExpExp f (App (Var m) e) | m == 'map = …
      
      





それにもかかわらず、この新しい形式は引用であり、引用符括弧と同じルールに従います[| … |]



[| … |]



。 たとえば、これらの角かっこ内では使用できません(不可能: [| 'foo |]



)。 ただし、接着にはタイプQ …



必要なため、接着を適用できません(不可能: $( 'foo )



)。 Q …



さらに重要なことは、この形式は静的に定義され、明確な解釈で完全修飾名を返します。

Haskellの名前空間は、物事を少し複雑にします。 引用[| P |]



[| P |]



[| P |]



のデータコンストラクターを意味し、 [t| P |]



[t| P |]



は、タイプP



コンストラクターを意味しますP



したがって、「円滑化された引用」には、これらのエンティティを分離する同じ方法が必要です。 型コンテキストでは、2つの単一引用符のみが使用されます。

最後に解析される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



関数を個別に含めて、元のコードに(読みやすさを向上させるために)いくつかの変更を加えました。




  {-# 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]
      
      







  {-# 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公式ページをご覧になることをお勧めします。ここでは、他の記事や多くの例へのリンクを見つけることができます。



All Articles