2012年12月17日月曜日

TemplateHaskellを調べてみた

TemplateHaskellでちょっと理解したことをメモ

ここんところTemplateHaskellでコケてしまったので忘れないうちにメモ

TemplateHaskellとは

Haskellのドキュメントには「Template Haskell is a GHC extension to Haskell that adds compile-time metaprogramming facilities.」ってかかれてて、GHCのextensionでコンパイルのメタプログラミングを出きるようにする機能らしいが何が何だか解らないので簡単なところで調べてみた
マクロみたいなもので抽象構文木(AST)として記述するとか説明があるけどよく解らない...笑


ghciから使ってみる

ghciを起動するときに-XTemplateHaskellってオプションをつけて起動するか、ghciを起動した後に:setで足してもいい、それからモジュールを読み込む

GHCi, version 7.4.1: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude>
Prelude> :set -XTemplateHaskell
Prelude> :m +Language.Haskell.TH
Prelude Language.Haskell.TH>


いきなり使ってみるが...Q Expがshowのinstanceでないので叱られる始末
Prelude Language.Haskell.TH> [e|1+1|]
<interactive>:15:1:
    No instance for (Show (Q Exp))
      arising from a use of `print'
    Possible fix: add an instance declaration for (Show (Q Exp))
    In a stmt of an interactive GHCi command: print it
Prelude Language.Haskell.TH>


とりあえずこいつの型を調べてみると、Q Expってなに?そもそもQってなに?
Prelude Language.Haskell.TH> :t [e|1+1|]
[e|1+1|] :: Q Exp
Prelude Language.Haskell.TH>


ここでとりあえずモジュール一覧を確認、見たところでほとんど解らない...何かQはnewtypeされててMonadクラスとFunctorのクラスのinstanceになってるらしい
Prelude Language.Haskell.TH> :browse
data Body = GuardedB [(Guard, Exp)] | NormalB Exp
type BodyQ = Q Body
data Callconv = CCall | StdCall
data Clause = Clause [Pat] Body [Dec]
type ClauseQ = Q Clause
data Con
  = NormalC Name [Language.Haskell.TH.Syntax.StrictType]
  | RecC Name [Language.Haskell.TH.Syntax.VarStrictType]

...
...

newtype Q a
  = Language.Haskell.TH.Syntax.Q {
       Language.Haskell.TH.Syntax.unQ :: forall (m :: * -> *).Language.Haskell.TH.Syntax.Quasi m => m a}
instance Monad Q -- Defined in `Language.Haskell.TH.Syntax'
instance Functor Q -- Defined in `Language.Haskell.TH.Syntax'
...
...
...


次にExpってなにか調べてみる
Prelude Language.Haskell.TH> :i Exp
data Exp
  = VarE Name
  | ConE Name
  | LitE Lit
  | AppE Exp Exp
  | InfixE (Maybe Exp) Exp (Maybe Exp)
  | UInfixE Exp Exp Exp
  | ParensE Exp
  | LamE [Pat] Exp
  | TupE [Exp]
  | UnboxedTupE [Exp]
  | CondE Exp Exp Exp
  | LetE [Dec] Exp
  | CaseE Exp [Match]
  | DoE [Stmt]
  | CompE [Stmt]
  | ArithSeqE Range
  | ListE [Exp]
  | SigE Exp Type
  | RecConE Name [FieldExp]
  | RecUpdE Exp [FieldExp]
      -- Defined in `Language.Haskell.TH.Syntax'
instance Eq Exp -- Defined in `Language.Haskell.TH.Syntax'
instance Show Exp -- Defined in `Language.Haskell.TH.Syntax'
instance Ppr Exp -- Defined in `Language.Haskell.TH.Ppr'
Prelude Language.Haskell.TH>

なるほどExpとはそんな感じなのね、って解らない、挫折寸前でこのサイトを見つける「Basic Tutorial of Template Haskell」そこには4つほどタイプがあって

  • Expression quotations
[e|1+2|]って式を書くパターン
  • Declaration quotations
[d|x = 5]って定義を書くパターン
  • Type quotations
[t|Int|]って型を書くパターン
  • Pattern quotations
[p|(x,y)]ってパターンを書くパターン...笑

パターンって2回言った...で使ってたのがExpression quotationってやつ、で調べている過程でrunQっていう関数を発見、こいつは上のquotationを展開してくれるっぽい


runQで色々やってみる

[e|1+1|]はQ Expでイケそうな気がする
Prelude Language.Haskell.TH> :i runQ
runQ :: Language.Haskell.TH.Syntax.Quasi m => Q a -> m a
      -- Defined in `Language.Haskell.TH.Syntax'
Prelude Language.Haskell.TH>
Prelude Language.Haskell.TH> runQ [e|1+1|]
Loading package array-0.4.0.0 ... linking ... done.
Loading package deepseq-1.3.0.0 ... linking ... done.
Loading package containers-0.4.2.1 ... linking ... done.
Loading package pretty-1.1.1.0 ... linking ... done.
Loading package template-haskell ... linking ... done.
InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 1)))
Prelude Language.Haskell.TH>
Prelude Language.Haskell.TH> let ex = [e|1+1|]
Prelude Language.Haskell.TH> :t ex
ex :: Q Exp
Prelude Language.Haskell.TH>
Prelude Language.Haskell.TH> $(st)
2
Prelude Language.Haskell.TH>
Prelude Language.Haskell.TH> :t InfixE
InfixE :: Maybe Exp -> Exp -> Maybe Exp -> Exp
Prelude Language.Haskell.TH>
で$()で実行されるのか

もうちょいいじってみる
Prelude Language.Haskell.TH>
Prelude Language.Haskell.TH> $(return (InfixE (Just (LitE (IntegerL 1))) (VarE (mkName "+")) (Just (LitE (IntegerL 1)))))
2
Prelude Language.Haskell.TH>

Qモナド?辺りに包んで返せば同じ結果になる、要するにコンパイル時にソースコードに掛かれた[|expression|]を展開してくれる機能なのか、ちなみに[]は「オックスフォード角括弧」って言うらしい、覚えきれない、難しいのですが他のタイプも調べてみます。

0 件のコメント:

コメントを投稿