Làm thế nào để dẫn xuất hoạt động trong Haskell?


104

Các kiểu dữ liệu đại số (ADT) trong Haskell có thể tự động trở thành các thể hiện của một số typeclasse (nhưShow,Eq) bằng cách lấy từ chúng.

data  Maybe a  =  Nothing | Just a
  deriving (Eq, Ord)

Câu hỏi của tôi là, điều này derivinghoạt động như thế nào, tức là làm thế nào Haskell biết cách triển khai các chức năng của typeclass dẫn xuất cho ADT dẫn xuất?

Ngoài ra, tại sao chỉ derivinggiới hạn cho một số kính in nhất định? Tại sao tôi không thể viết typeclass của riêng mình mà có thể bắt nguồn?

Câu trả lời:


75

Câu trả lời ngắn gọn là, ma thuật :-). Điều này có nghĩa là dẫn xuất tự động được đưa vào thông số kỹ thuật Haskell, và mọi trình biên dịch có thể chọn triển khai nó theo cách riêng của mình. Tuy nhiên, có rất nhiều công việc về cách làm cho nó có thể mở rộng được.

Derive là một công cụ để Haskell cho phép bạn viết các cơ chế dẫn xuất của riêng mình.

GHC từng cung cấp một phần mở rộng lớp kiểu dẫn xuất được gọi là Lớp Chung , nhưng nó hiếm khi được sử dụng, vì nó hơi yếu. Điều đó hiện đã được giải quyết và công việc đang diễn ra để tích hợp một cơ chế dẫn xuất chung mới như được mô tả trong bài báo này: http://www.dreixel.net/research/pdf/gdmh.pdf

Để biết thêm về điều này, hãy xem:


2
Xem thêm StandaloneDerivingtrong hướng dẫn ghc
AndrewC

1
Chỉ để biết thông tin của bạn, điều kỳ diệu được chỉ định rõ ràng tại haskell.org/onlinereport/haskell2010/haskellch11.html .
Wong Jia Hau,


5

Có thể sử dụng Template Haskell để tạo các khai báo cá thể theo cách tương tự với các mệnh đề dẫn xuất.

Ví dụ sau đây bị đánh cắp một cách đáng xấu hổ từ Haskell Wiki :

Trong ví dụ này, chúng tôi sử dụng mã Haskell sau

$(gen_render ''Body)

để tạo ra ví dụ sau:

instance TH_Render Body where
  render (NormalB exp) = build 'normalB exp
  render (GuardedB guards) = build 'guardedB  guards

Hàm gen_rendertrên được định nghĩa như sau. (Lưu ý rằng mã này phải nằm trong mô-đun riêng biệt với cách sử dụng ở trên).

-- 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))

Trong đó sử dụng các chức năng và kiểu sau.

Đầu tiên, hãy nhập một số từ đồng nghĩa để làm cho mã dễ đọc hơn.

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)]

Chức năng tái sử dụng chính. Chúng tôi chuyển nó vào danh sách các hàm để tạo ra các hàm của cá thể.

-- 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)

Một chức năng trợ giúp ở trên.

-- 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

Và một số mã trợ giúp mượn được lấy từ 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.