Haskell의 ADT ( Algebraic Data Types )는 자동으로 일부 유형 클래스 (예 :Show
,Eq
)에서 파생 되어 인스턴스가 될 수있습니다.
data Maybe a = Nothing | Just a
deriving (Eq, Ord)
내 질문은 이것이 어떻게 deriving
작동하는지입니다. 즉, Haskell이 파생 ADT를 위해 파생 된 유형 클래스의 함수를 구현하는 방법을 어떻게 알 수 있습니까?
또한 왜 deriving
특정 유형 클래스로만 제한됩니까? 파생 될 수있는 고유 한 유형 클래스를 작성할 수없는 이유는 무엇입니까?
답변
짧은 대답은 마술입니다 :-). 즉, 자동 파생이 Haskell 사양에 포함되어 있으며 모든 컴파일러가 자체 방식으로 구현하도록 선택할 수 있습니다. 그러나 확장 가능하게 만드는 방법에 대한 많은 작업이 있습니다.
Derive 는 고유 한 파생 메커니즘을 작성할 수 있도록하는 Haskell 용 도구입니다.
GHC는 Generic Classes 라는 파생 가능한 유형 클래스 확장을 제공하는 데 사용 되었지만 다소 약해서 거의 사용되지 않았습니다. 이제 제거되었으며이 문서에 설명 된대로 새로운 일반 파생 메커니즘을 통합하기위한 작업이 진행 중입니다. http://www.dreixel.net/research/pdf/gdmh.pdf
이에 대한 자세한 내용은 다음을 참조하십시오.
답변
Haskell 98 보고서에서 :
Prelude에서 파생 인스턴스가 허용되는 유일한 클래스는 Eq, Ord, Enum, Bounded, Show 및 Read …입니다.
이러한 유형 클래스를 파생하는 방법에 대한 설명은 다음과 같습니다. http://www.haskell.org/onlinereport/derived.html#derived-appendix
답변
deriving-clauses와 유사한 방식으로 Template Haskell 을 사용하여 인스턴스 선언을 생성 할 수 있습니다 .
다음 예제는 Haskell Wiki 에서 뻔뻔스럽게 도난당했습니다 .
이 예에서는 다음 Haskell 코드를 사용합니다.
$(gen_render ''Body)
다음 인스턴스를 생성합니다.
instance TH_Render Body where render (NormalB exp) = build 'normalB exp render (GuardedB guards) = build 'guardedB guards
gen_render
위 의 기능 은 다음과 같이 정의됩니다. (이 코드는 위 사용법과는 별도의 모듈에 있어야합니다.)-- Generate an intance of the class TH_Render for the type typName gen_render :: Name -> Q [Dec] gen_render typName = do (TyConI d) <- reify typName -- Get all the information on the type (type_name,_,_,constructors) <- typeInfo (return d) -- extract name and constructors i_dec <- gen_instance (mkName "TH_Render") (conT type_name) constructors -- generation function for method "render" [(mkName "render", gen_render)] return [i_dec] -- return the instance declaration -- function to generation the function body for a particular function -- and constructor where gen_render (conName, components) vars -- function name is based on constructor name = let funcName = makeName $ unCapalize $ nameBase conName -- choose the correct builder function headFunc = case vars of [] -> "func_out" otherwise -> "build" -- build 'funcName parm1 parm2 parm3 ... in appsE $ (varE $ mkName headFunc):funcName:vars -- put it all together -- equivalent to 'funcStr where funcStr CONTAINS the name to be returned makeName funcStr = (appE (varE (mkName "mkName")) (litE $ StringL funcStr))
다음 기능과 유형을 사용합니다.
먼저 코드를 더 읽기 쉽게 만들기위한 유형 동의어입니다.
type Constructor = (Name, [(Maybe Name, Type)]) -- the list of constructors type Cons_vars = [ExpQ] -- A list of variables that bind in the constructor type Function_body = ExpQ type Gen_func = Constructor -> Cons_vars -> Function_body type Func_name = Name -- The name of the instance function we will be creating -- For each function in the instance we provide a generator function -- to generate the function body (the body is generated for each constructor) type Funcs = [(Func_name, Gen_func)]
재사용 가능한 주요 기능. 인스턴스의 함수를 생성하기 위해 함수 목록을 전달합니다.
-- construct an instance of class class_name for type for_type -- funcs is a list of instance method names with a corresponding -- function to build the method body gen_instance :: Name -> TypeQ -> [Constructor] -> Funcs -> DecQ gen_instance class_name for_type constructors funcs = instanceD (cxt []) (appT (conT class_name) for_type) (map func_def funcs) where func_def (func_name, gen_func) = funD func_name -- method name -- generate function body for each constructor (map (gen_clause gen_func) constructors)
위의 도우미 기능.
-- Generate the pattern match and function body for a given method and -- a given constructor. func_body is a function that generations the -- function body gen_clause :: (Constructor -> [ExpQ] -> ExpQ) -> Constructor -> ClauseQ gen_clause func_body data_con@(con_name, components) = -- create a parameter for each component of the constructor do vars <- mapM var components -- function (unnamed) that pattern matches the constructor -- mapping each component to a value. (clause [(conP con_name (map varP vars))] (normalB (func_body data_con (map varE vars))) []) -- create a unique name for each component. where var (_, typ) = newName $ case typ of (ConT name) -> toL $ nameBase name otherwise -> "parm" where toL (x:y) = (toLower x):y unCapalize :: [Char] -> [Char] unCapalize (x:y) = (toLower x):y
그리고 Syb III / replib 0.2에서 가져온 일부 도우미 코드를 빌 렸습니다.
typeInfo :: DecQ -> Q (Name, [Name], [(Name, Int)], [(Name, [(Maybe Name, Type)])]) typeInfo m = do d <- m case d of d@(DataD _ _ _ _ _) -> return $ (simpleName $ name d, paramsA d, consA d, termsA d) d@(NewtypeD _ _ _ _ _) -> return $ (simpleName $ name d, paramsA d, consA d, termsA d) _ -> error ("derive: not a data type declaration: " ++ show d) where consA (DataD _ _ _ cs _) = map conA cs consA (NewtypeD _ _ _ c _) = [ conA c ] {- This part no longer works on 7.6.3 paramsA (DataD _ _ ps _ _) = ps paramsA (NewtypeD _ _ ps _ _) = ps -} -- Use this on more recent GHC rather than the above paramsA (DataD _ _ ps _ _) = map nameFromTyVar ps paramsA (NewtypeD _ _ ps _ _) = map nameFromTyVar ps nameFromTyVar (PlainTV a) = a nameFromTyVar (KindedTV a _) = a termsA (DataD _ _ _ cs _) = map termA cs termsA (NewtypeD _ _ _ c _) = [ termA c ] termA (NormalC c xs) = (c, map (\x -> (Nothing, snd x)) xs) termA (RecC c xs) = (c, map (\(n, _, t) -> (Just $ simpleName n, t)) xs) termA (InfixC t1 c t2) = (c, [(Nothing, snd t1), (Nothing, snd t2)]) conA (NormalC c xs) = (simpleName c, length xs) conA (RecC c xs) = (simpleName c, length xs) conA (InfixC _ c _) = (simpleName c, 2) name (DataD _ n _ _ _) = n name (NewtypeD _ n _ _ _) = n name d = error $ show d simpleName :: Name -> Name simpleName nm = let s = nameBase nm in case dropWhile (/=':') s of [] -> mkName s _:[] -> mkName s _:t -> mkName t