テンプレートHaskellの紹介。 パート1.必要最小限

このテキストは、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を使用するために知っておく必要がある最低限の事項は、次のトピックです。
  1. テンプレートでのHaskellコードの表現方法(TH関数)
  2. 引用モナドを使用して名前を統一する方法
  3. 生成されたTHコードがプログラムに挿入される方法


Haskellコードがテンプレートでどのように表示されるか


テンプレートHaskellでは、Haskellコードのフラグメントは、従来の代数データ型を使用して表されます 。 これらの型はHaskell構文に従って構築され、特定のコードの抽象構文ツリー (AST-抽象構文ツリー)を表します。 式を表すExp型、パターンのPat 、リテラルのLit 、宣言のDecTypeなどがあります。 これらすべてのタイプの定義は、 Language.Haskell.TH.Syntaxモジュールのドキュメントに記載されています。 これらはHaskell構文のルールに従って相互接続されているため、Haskellコードのフラグメントを表す値を構築できます。 以下に簡単な例を示します。
私たちの生活を簡素化するために、 Exp型のすべてのコンストラクターの名前はEで終わり、 Pat型のコンストラクターの名前はPで終わります。 上記で使用されたmkName関数は、通常の文字列( String )からName型(識別子を表す)の値を、その内容を名前として作成します。
そのため、Haskellコードを作成するために、TH関数は、このコードの表現であるExpDecPatまたは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が関数構成演算子の値をオーバーライドするのと同様に、この文字の値を中置演算子としてオーバーライドします. 」 演算子が必要な場合は、文字をスペースで囲む必要があります。
貼り付けが表示される場合があります

また、それを知っている必要があります

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") 

続く


次のセクションでは、より興味深く高度なトピックを取り上げます。
  1. 引用モナド
  2. 引用符ブラケット
  3. 実体化(具体化)
  4. エラーメッセージと回復
  5. デバッグ
そして、冒頭で述べたprintfderiveShow例はderiveShowます。

PSこれは私の最初の翻訳なので、記事のトピックに関する建設的な批判と実質的な議論を期待しています。

更新:
パート2.コード引用ツール
パート3. THのその他の側面

Source: https://habr.com/ru/post/J131998/


All Articles