2012-04-02 71 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中来解决其他模块。

如果无法生成预先计算的数据,例如问题,我想了解更多关于TH的理论限制。

+1

我发现......令人惊讶的是,那是行不通的。也许你还需要打开QuasiQuotes? – 2012-04-02 16:26:25

回答

4

这确实是舞台限制的问题。正如哈马尔指出的那样,问题在于拨打getDescription

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

功能getDescription过载,并且所述编译器选择基于其参数的类型的实施。

class HasDescription a where 
    getDescription :: a -> Description 

类型类根据类型重载。将t转换为类型的唯一方法是编译它。但编译它会将放入编译的程序中。对getDescription的调用在编译时间处运行,因此它无法访问该类型。

如果你真的想在模板Haskell中评估getDescription,你必须编写你自己的实现getDescription,它读取模板Haskell数据结构,它在编译时可用。

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牛津括号内结合修复:

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

当然,这意味着,这将是所产生的代码的一部分,但至少对于这种情况下,当不可没关系。

+1

感谢您的建议。我想早些时候在括号内部移动,但是代码会被频繁调用,所以它必须很快。标准基准测试表明,使用let的getDescription比已经修改过的描述要慢(实际上我在其他函数和数据类型上尝试过 - HasDescription只是一种简化)。 – Boris 2012-04-02 19:49:13