2012-04-02 8 views
9

私は、その出力がクラスインスタンスに格納されているデータ型フィールドの説明に依存するコードジェネレータを作成しています。しかし、私は、TH生成の引数で関数を実行する方法を見つけることができません。GHCステージ制限を回避するにはどうすればよいですか?

{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-} 
module Generator where 
import Language.Haskell.TH 
import Language.Haskell.TH.Syntax 

data Description = Description String [Description] deriving Show 

class HasDescription a where 
    getDescription :: a -> Description 

instance HasDescription Int where 
    getDescription _ = Description "Int" [] 

instance (HasDescription a, HasDescription b) => HasDescription (a, b) where 
    getDescription (_ :: (a, b)) = Description "Tuple2" [getDescription (undefined :: a), getDescription (undefined :: b)] 

-- | creates instance of HasDescription for the passed datatype changing descriptions of its fields 
mkHasDescription :: Name -> Q [Dec] 
mkHasDescription dName = do 
    reify dName >>= runIO . print 
    TyConI (DataD cxt name tyVarBndr [NormalC cName types] derives) <- reify dName 
    -- Attempt to get description of data to modify it. 
    let mkSubDesc t = let Description desc ds = getDescription (undefined :: $(return t)) in [| Description $(lift $ desC++ "Modified") $(lift ds) |] 

    let body = [| Description $(lift $ nameBase dName) $(listE $ map (mkSubDesc . snd) types) |] 
    getDescription' <- funD 'getDescription [clause [wildP] (normalB body) []] 
    return [ InstanceD [] (AppT (ConT ''HasDescription) (ConT dName)) [getDescription'] ] 

別のモジュールが発電機

{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-} 
import Generator 

data MyData = MyData Int Int 

mkHasDescription ''MyData 

{- the code I want to generate 
instance HasDescription MyData where 
    getDescription _ = Description "MyData" [Description "IntModified" [], Description "IntModified" []] 
-} 

を使用しようとすると、エラー

Generator.hs:23:85: 
GHC stage restriction: `t' 
    is used in a top-level splice or annotation, 
    and must be imported, not defined locally 
In the first argument of `return', namely `t' 
In the expression: return t 
In an expression type signature: $(return t) 

編集が表示されます。尋ねるとき

私は問題はちょうど私からといって登場していると考えがTHの中で重要な何かを把握しておらず、いくつかの機能を他のモジュール。

質問の例のように事前計算データを生成することができない場合は、THの理論的な制限についてもっと学びたいと思います。

もちろん
let mkSubDesc t = [| let Description desc ds = getDescription (undefined :: $(return t)) 
        in Description (desC++ "Modified") ds |] 

が、これはこれは、生成されたコードの一部になることを意味するが、少なくともこのような場合のために、そのshouldn」:

+1

私が見つけたのは...驚くべきことですが、それはうまくいかないでしょう。おそらくQuasiQuotesを有効にする必要がありますか? –

答えて

4

これは実際にステージ制限の問題です。ハンマーが指摘したように、この問題はgetDescriptionへの呼び出しにあります。

let mkSubDesc t = ... getDescription (undefined :: $(return t)) ... 

機能getDescriptionがオーバーロードされ、コンパイラはその引数のタイプに基づいて実装を選択します。

タイプクラスはタイプに基づいてオーバーロードされます。 tを型に変換する唯一の方法は、それをコンパイルすることです。コンパイルされたプログラムにタイプが入ります。 getDescriptionの呼び出しは、コンパイル時にと実行されるため、その型へのアクセス権はありません。

テンプレートHaskellで実際にgetDescriptionを評価したい場合は、コンパイル時に使用可能なテンプレートHaskellデータ構造を読み取るgetDescriptionの独自の実装を作成する必要があります。

getDescription2 :: Type -> Q Description 
getDescription2 t = cases con [ ([t| Int |], "Int") 
           , (return (TupleT 2), "Tuple") 
           ] 
    where 
    (con, ts) = fromApp t 
    fromApp (AppT t1 t2) = let (c, ts) = fromApp t1 in (c, ts ++ [t2]) 
    fromApp t = (t, []) 
    cases x ((make_y, name):ys) = do y <- make_y 
            if x == y 
             then do ds <- mapM getDescription2 ts 
               return $ Description name ds 
             else cases x ys 
    cases x [] = error "getDescription: Unrecognized type" 
7

あなたはオックスフォード括弧内のバインディングletを移動することによって、それを修正することができますt問題。

+1

アドバイスをいただきありがとうございます。私は早い段階で括弧内を動かすことを考えましたが、コードは頻繁に呼び出されるので速くなければなりません。 Criterionベンチマークは、letを使ったgetDescriptionが既に変更された記述よりも遅いことを示しています(実際には、他の関数やデータ型でHasDescriptionを単純化してみました)。 – Boris

関連する問題