如何规避GHC阶段限制?

时间:2012-04-02 16:02:15

标签: haskell template-haskell

我正在编写一个代码生成器,其输出依赖于存储在其类实例中的数据类型字段描述。但是,我找不到如何使用TH生成的参数运行函数。

{-# 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'] ]

当另一个模块尝试使用Generator

{-# 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的理论限制。

2 个答案:

答案 0 :(得分:7)

您可以通过移动牛津括号内的let绑定来修复它:

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

当然,这意味着这将是生成的代码的一部分,但至少在这种情况下,这应该不重要。

答案 1 :(得分:4)

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

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

函数getDescription被重载,编译器根据其参数的类型选择实现。

class HasDescription a where
  getDescription :: a -> Description

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

如果你真的想在Template Haskell中评估getDescription,你必须编写自己的getDescription实现,它读取编译时可用的Template 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"