2012-04-02 2 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

이것은 실제로 스테이지 제한과 관련된 문제입니다. hammar가 지적했듯이 문제는 getDescription으로 전화를 겁니다.

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

함수 getDescription 과부하되고, 컴파일러는 인자의 유형에 따라 구현을 선택한다.

유형 클래스는 유형에 따라 오버로드됩니다. t을 유형으로 변환하는 유일한 방법은 컴파일하는 것입니다. 그러나 그것을 컴파일하면 이 컴파일 된 프로그램에 저장됩니다. getDescription에 대한 호출은 일 때 으로 실행되므로 해당 유형에 액세스 할 수 없습니다.

템플릿 하스켈에서 실제로 getDescription을 평가하려면 컴파일시 사용할 수있는 템플릿 하스켈 데이터 구조를 읽는 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를 이동하여 문제를 해결할 수 있습니다 문제 없습니다.

+1

조언 해 주셔서 감사합니다. 나는 이전에 괄호 안을 움직이는 것에 대해 생각했다. 그러나 코드는 자주 호출되어 빠를 것이다. 기준 벤치 마크에 따르면 let을 사용하는 getDescription은 이미 수정 된 설명보다 느립니다 (실제로 다른 함수 및 데이터 유형 - HasDescription은 단순화 임). – Boris