9

我正在编写一个代码生成器,其输出取决于存储在其类实例中的数据类型字段描述。但是,我找不到如何使用 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'] ]

当另一个模块尝试使用生成器时

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

4

2 回答 2

7

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

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

Of course, this means that this will be part of the generated code, but at least for this case, that shouldn't matter.

于 2012-04-02T16:49:24.823 回答
4

This is indeed an issue with the stage restriction. The problem, as hammar pointed out, lies with the call to getDescription.

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

The function getDescription is overloaded, and the compiler chooses the implementation based on the type of its argument.

class HasDescription a where
  getDescription :: a -> Description

Type classes are overloaded based on types. The only way to convert t to a type is to compile it. But compiling it puts the type in the compiled program. The call to getDescription runs at compile time, so it has no access to that type.

If you really want to evaluate getDescription in Template Haskell, you have to write your own implementation of getDescription that reads the Template Haskell data structure that is available at compile time.

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"
于 2012-04-02T20:49:16.597 回答