2017-02-03 6 views
2

Я изучаю основные классы типа и написал свою собственную реализацию functor для моего типа Test a (ведет себя так же, как Maybe):Написание экземпляра экземпляра generic для конструкторов типов?

data Test a = Test a | Emp 

class FC c a where 
    t :: (a -> b) -> c a -> c b 

instance FC Test a where 
    t f (Test a) = Test (f a) 
    t f (Emp) = Emp 

instance FC Maybe a where 
    t f (Just a) = Just (f a) 
    t f (Nothing) = Nothing 

Можно ли реализовать что-то вроде:

instance FC c where 
    t f (c v) = c (f v) 

Ошибка:

Parse error in pattern: c 

Другими словами, отвлечь конструктор типа, заменить с c и v, поэтому создается общий экземпляр, который может быть применен к любому значению с помощью контекста?

ответ

5

Как вы узнали, c a не является синтаксически действующим шаблоном. Но вместо того, чтобы читать ваш вопрос в качестве предложения функции: как это будет работать? Не каждый Functor имеет одноэлементный конструктор, который можно сопоставить в соответствии с вашим шаблоном. Некоторые примеры:

data Pair a = Pair a a -- more than one element 
instance Functor Pair where 
    fmap f (Pair x y) = Pair (f x) (f y) 

data Proxy a = Proxy -- no elements 
instance Functor Proxy where 
    fmap f Proxy = Proxy 

newtype Cont r a = Cont { runCont :: (a -> r) -> r } -- element appears in a double-negative position 
instance Functor (Cont r) where 
    fmap f (Cont g) = Cont (g . (. f)) 

В любом случае, я не думаю, что идея «общего экземпляра» действительно имеет смысл. Экземпляр - это то место, где вы указываете свой код типа. (Он должен куда-то уйти!)

Если вы хотите приложить меньше усилий при написании экземпляров Functor, вы можете использовать расширение DeriveFunctor GHC.

{-# LANGUAGE DeriveFunctor #-} 

data Pair a = Pair a a deriving Functor 

data Proxy a = Proxy deriving Functor 

newtype Cont r a = Cont { runCont :: (a -> r) -> r } deriving Functor 
2

Насколько я знаю, это невозможно, просто потому, что может быть несколько конструкторов, и неизвестно, может ли общий конструктор Foo использовать любой атрибут как тип.

Скажем, например, у вас есть тип, который называется:

data Foo a = Bar Int | Qux a 

Теперь это означает, что вы не можете абстрагироваться от конструктора. Пока это Qux, проблем нет, но Bar всегда ожидает Int и, следовательно, будет ошибка. Поскольку вы здесь определяете instance по любым типам c, будут случаи, когда это не сработает. Обратите также внимание на то, что c в вашей декларации instance не имеет никакого отношения к c в вашем определении t. Другими словами: конструкторы могут подразумевать ограничения типа, поэтому вы не можете просто их исключить.

Замечание о вашем вопросе является то, что вы можете обобщить как вам class дефиниции и instance:

class FC c where 
    t :: (a -> b) -> c a -> c b 

instance FC Test where 
    t f (Test a) = Test (f a) 
    t f Emp = Emp 

Таким образом, вы можете удалить a в class определении. Это не соответствует вашим вопросам, так как здесь вы говорите, что он может работать для любого a. Если вы определяете класс FC c a, вы можете решить, для каких a s вы хотите реализовать instance.

4

Вы можете сделать что-то очень общие с помощью GHC.Generic.Вот неполный пример для общего FC определения класса (это именно то, что делает generic-deriving пакета):

Сначала некоторые расширения и импорт дженерик машин

{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE DefaultSignatures #-} 
{-# LANGUAGE DeriveGeneriC#-} 
{-# LANGUAGE TypeOperators #-} 

import GHC.Generics 

Затем мы определяем класс, который отражает вашу FC но у нас есть только экземпляры для родовых типов

class GFC c where 
    gt :: (a -> b) -> c a -> c b 

-- Constructors without arguments (Empty) 
instance GFC U1 where 
    gt _ U1 = U1 

-- Constructors where the parameter appears (Test a) 
instance GFC Par1 where 
    gt f (Par1 a) = Par1 (f a) 

-- Sums (| in datatype definitions) 
instance (GFC f, GFC g) => GFC (f :+: g) where 
    gt f (L1 a) = L1 (gt f a) 
    gt f (R1 a) = R1 (gt f a) 

-- Meta information wrapper 
instance GFC f => GFC (M1 i c f) where 
    gt f (M1 a) = M1 (gt f a) 

-- ... the rest of the instances for the generic types here. 
-- But these 4 instances are all that is needed for your `Test` type. 

Тогда вы можете иметь реализацию по умолчанию для FC исходя из указанных выше «общий» FC:

class FC c where 
    t :: (a -> b) -> c a -> c b 
    default -- DefaultSignatures allows us to do this 
    t :: (Generic1 c, GFC (Rep1 c)) => (a -> b) -> c a -> c b 
    t f = to1 . gt f . from1 
    -- turn something with Generic1 into its generic representation, 
    -- use the generic `gt` and then turn it back into its actual 
    -- representation 

data Test a = Test a | Empty 
    deriving (Generic1, Show) 

instance FC Test 

И это работает:

GHCI> t (==0) (Test (1 :: Int)) 
Test False