サーチ…


備考

テンプレートハスケルとは何ですか?

テンプレートHaskellは、GHC Haskellに組み込まれているテンプレートのメタプログラミング機能を指します。元の実装を記述した論文は、 ここで見つけることができます

ステージとは何ですか? (または、ステージ制限は何ですか?)

ステージとはコードが実行されるときを指します。通常、コードは実行時にのみ展開されますが、テンプレートHaskellではコンパイル時にコードを実行できます。 「標準」コードはステージ0で、コンパイル時コードはステージ1です。

ステージ制限とは、ステージ0のプログラムがステージ1で実行されないということです。これは、コンパイル時に(メタプログラムだけでなく) 通常のプログラムを実行できることと同等です。

規約(および実装の単純化のため)では、現在のモジュール内のコードは常にステージ0であり、他のすべてのモジュールからインポートされたコードはステージ1です。このため、他のモジュールからの式だけをスプライスできます。

ステージ1のプログラムはQ ExpQ Typeなどのステージ0式であることに注意してください。逆は真ではありません。タイプQ Expすべての値(ステージ0プログラム)がステージ1プログラムではなく、

さらに、スプライスは入れ子にすることができるので、識別子は1より大きいステージを持つことができます。ステージ制限は一般化できます。ステージnプログラムはm> nのどのステージでも実行できません。たとえば、特定のエラーメッセージで、1より大きいステージへの参照を参照できます。

>:t [| \x -> $x |]

<interactive>:1:10: error:
    * Stage error: `x' is bound at stage 2 but used at stage 1
    * In the untyped splice: $x
      In the Template Haskell quotation [| \ x -> $x |]

テンプレートを使うHaskellは無関係の識別子からスコープ内のエラーを引き起こしますか?

通常、単一のHaskellモジュール内のすべての宣言は、すべてが相互に再帰的であるとみなすことができます。言い換えれば、すべてのトップレベルの宣言は、単一のモジュール内の他のすべての宣言の範囲内にあります。 Template Haskellを有効にすると、有効範囲規則が変更されます。代わりに、モジュールはTHスプライスで区切られたコードのグループに分割され、各グループは相互に再帰的であり、各グループはすべてのグループのスコープ内にあります。

Qタイプ

Language.Haskell.TH.Syntaxで定義されたQ :: * -> *型のコンストラクタは、計算が実行されるモジュールのコンパイル時の環境にアクセスできる抽象型です。 Q型はまた、THによる名前の取り込みと呼ばれる変数の置換を処理します( ここで説明ます)。すべてのスプライスは、一部のX QX型を持ちます。

コンパイル時の環境は次のとおりです。

  • スコープ内の識別子および前記識別子に関する情報を含み、
    • 関数の種類
    • コンストラクタの型とソースデータ型
    • 型宣言(クラス、型族)の完全指定
  • スプライスが発生するソースコード内の場所(行、列、モジュール、パッケージ)
  • 機能の固定(GHC 7.10)
  • 有効なGHC拡張(GHC 8.0)

Q型には、関数newName :: String -> Q Name使って新しい名前を生成する機能もあります。名前は暗黙のうちに束縛されていないので、ユーザーはそれ自身をバインドしなければならないので、名前の使用結果が有効範囲に入っていることを確認することはユーザーの責任です。

QFunctor,Monad,Applicativeインスタンスがあり、これはLanguage.Haskell.TH.Libで提供されているコンビネータとともに、 Q値を操作するためのメインインターフェイスです。フォームのTH astのすべてのコンストラクタのヘルパ関数を定義します:

LitE :: Lit -> Exp
litE :: Lit -> ExpQ

AppE :: Exp -> Exp -> Exp 
appE :: ExpQ -> ExpQ -> ExpQ

ExpQTypeQDecsQ 、およびPatQは、通常Qタイプ内に格納されるASTタイプの同義語であることに注意してください。

THライブラリは、機能を提供runQ :: Quasi m => Q a -> ma 、そしてインスタンスがQuasi IOそれがあることと思われるので、 Qタイプはちょうど空想であるIO 。しかし、使用runQ :: Q a -> IO a生産IO任意のコンパイル時の環境へのアクセス権を持っていないアクションを-これは実際にのみ使用可能ですQタイプ。そのようなIOアクションは、前記環境にアクセスしようとすると実行時に失敗する。

n-arityカレー

おなじみの

curry :: ((a,b) -> c) -> a -> b -> c
curry = \f a b -> f (a,b)

関数は、任意のアリティのタプルに一般化することができます。例:

curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e 

しかし、手作業で2のタプル(例えば)20のタプルの関数を書くことは面倒です(プログラム内の20タプルの存在がレコードで修正されるべき設計上の問題をほぼ確実に示しているという事実は無視してください)。

テンプレートHaskellを使って、任意のnに対してこのようなcurryN関数を生成することができます:

{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (replicateM) 
import Language.Haskell.TH (ExpQ, newName, Exp(..), Pat(..))
import Numeric.Natural (Natural) 

curryN :: Natural -> Q Exp

curryN関数は自然数を取り、そのcurryNのカリー関数をHaskell ASTとして生成します。

curryN n = do
  f  <- newName "f"
  xs <- replicateM (fromIntegral n) (newName "x")

最初に、関数の引数のそれぞれに新しい型の変数を生成します.1つは入力関数用、もう1つは関数の引数用です。

  let args = map VarP (f:xs)

argsはパターンf x1 x2 .. xn表しf x1 x2 .. xn 。パターンは別個の構文エンティティであることに注意してください。この同じパターンをラムダ、関数バインディング、またはletバインディングのLHS(これはエラーになります)に配置することもできます。

      ntup = TupE (map VarE xs)

この関数は、引数のシーケンスから引数タプルを構築する必要があります。これは、ここで行ったことです。パターン変数( VarP )と式変数( VarE )の区別に注意してください。

  return $ LamE args (AppE (VarE f) ntup)

最後に、私たちが生成する値は、AST \f x1 x2 .. xn -> f (x1, x2, .. , xn)です。

引用符と '持ち上げた'コンストラクタを使ってこの関数を書くこともできます:

...
import Language.Haskell.TH.Lib  

curryN' :: Natural -> ExpQ
curryN' n = do
  f  <- newName "f"
  xs <- replicateM (fromIntegral n) (newName "x")
  lamE (map varP (f:xs)) 
        [| $(varE f) $(tupE (map varE xs)) |]

引用は構文的に有効でなければならないので、 [| \ $(map varP (f:xs)) -> .. |]通常のHaskellではパターンの 'リスト'を宣言する方法がないため、 \ var -> .. [| \ $(map varP (f:xs)) -> .. |]は無効[| \ $(map varP (f:xs)) -> .. |]上記は\ var -> ..と解釈され、スプライスされた式は、パターンのリストではなく、単一のパターンであるPatQ型を持つと予想されます。

最後に、このTH関数をGHCiにロードすることができます。

>:set -XTemplateHaskell
>:t $(curryN 5)
$(curryN 5)
  :: ((t1, t2, t3, t4, t5) -> t) -> t1 -> t2 -> t3 -> t4 -> t5 -> t
>$(curryN 5) (\(a,b,c,d,e) -> a+b+c+d+e) 1 2 3 4 5
15

この例は主にここから適応されています

テンプレートHaskellとQuasiquotesの構文

テンプレートHaskellは、 -XTemplateHaskell GHC拡張によって有効になります。この拡張により、このセクションで詳しく説明されているすべての構文機能が使用可能になります。 Template Haskellの詳細は、 ユーザーガイドに記載されています

スプライス

  • スプライスは、 $(...)と書かれたTemplate Haskellによって有効にされた新しい構文エンティティ$(...)ここで、 (...)は式です。

  • $と式の最初の文字の間にスペースを入れてはいけません。そしてテンプレートHaskellは解析のオーバーライド$例えば-演算子f$g通常のように解析される($) fgテンプレートHaskellのを有効にして、それはスプライスとして解析される一方。

  • トップレベルにスプライスが現れると、 $を省略することができます。この場合、スプライスされた式は行全体です。

  • スプライスは、コンパイル時に実行されてHaskell ASTを生成するコードを表し、ASTはHaskellコードとしてコンパイルされ、プログラムに挿入されます

  • 式、パターン、型、およびトップレベルの宣言の代わりにスプライスを表示できます。スプライスされた式のタイプは、それぞれそれぞれQ ExpQ PatQ TypeQ [Decl]です。宣言スプライスはトップレベルにのみ表示され、他のスプライスは他の式、パターン、またはタイプの内側に表示されることに注意してください。

表現引用(注:準引用符ではない

  • 式引用は、次のいずれかの形式で記述された新しい構文エンティティです。

    • [e|..|]または[|..|] - ..は式であり、クォーテーションの型はQ Expです。
    • [p|..|] - ..はパターンであり、クォートの型はQ Patです。
    • [t|..|] - ..は型であり、引用符はQ Typeです。
    • [d|..|] - ..は宣言のリストであり、引用の型はQ [Dec]です。
  • 式引用は、コンパイル時間プログラムを取り、そのプログラムによって表されるASTを生成する。

  • スプライスなしで引用符で値(例: \x -> [| x |] )を使用すると、 \x -> [| $(lift x) |]ここで、 lift :: Lift t => t -> Q Expはクラスから来る

    class Lift t where
      lift :: t -> Q Exp
      default lift :: Data t => t -> Q Exp

入力されたスプライスと引用

  • 型付きスプライスは、前に述べた(型なし)スプライスと同様で、 $$(..)と書かれてい$$(..)ここで、 (..)は式です。

  • eがタイプQ (TExp a)場合、 $$eはタイプaます。

  • 型付き引用符は、形式[||..||]取り[||..||]ここで..a型の式です。結果の見積はタイプQ (TExp a)ます。

  • 型付き式は型なし式に変換できます: unType :: TExp a -> Exp

準疑問

  • QuasiQuotesは式の引用を一般化しています - 以前は、式の引用で使用されるパーサーは固定セット( e,p,t,d )のいずれかですが、QuasiQuotesではカスタムパーサーを定義してコンパイル時にコードを生成できます。疑似引用は、すべての同じ文脈で、通常の引用と同様に現れることがあります。

  • 準引用符は[iden|...|]と書かれてい[iden|...|]ここで、 idenLanguage.Haskell.TH.Quote.QuasiQuoter型の識別子です。

  • QuasiQuoterは、単純に4つのパーサーで構成されています。各パーサーは、引用が表示されるさまざまなコンテキストごとに1つずつあります。

    data QuasiQuoter = QuasiQuoter { quoteExp  :: String -> Q Exp,
                                     quotePat  :: String -> Q Pat,
                                     quoteType :: String -> Q Type,
                                     quoteDec  :: String -> Q [Dec] }

名前

  • Haskellの識別子は、 Language.Haskell.TH.Syntax.Name型で表されます。名前は、テンプレートHaskellのHaskellプログラムを表す抽象構文木の葉を形成します。

  • 現在スコープ内にある識別子は、 'eまたは'Tいずれかの名前に変換することができます。最初のケースでは、 eは式スコープで解釈され、2番目のケースではTは型スコープにあります(型と値のコンストラクタがHaskellでの共通性なしで名前を共有することを思い出してください)。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow