问题
I'm learning basic type classes and have written my own implementation of functor for my type Test a (behaves just like Maybe):
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
Is it possible to implement something like:
instance FC c where
t f (c v) = c (f v)
Error:
Parse error in pattern: c
In other words, abstract away the type constructor, replace with c and v, therefore creating a general instance that can be applied to any value with a context?
回答1:
As you've learned, c a is not a syntactically valid pattern. But reading your question instead as a feature proposal: How would that work? Not every Functor has a single-element constructor which can be mapped over according to your pattern. Some examples:
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))
In any case, I don't think the idea of a "generic instance" really makes sense. The instance is where you put your type-specific code. (It has to go somewhere!)
If you want to exert less effort in writing Functor instances you can use GHC's DeriveFunctor extension.
{-# 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:
You can do something very generic using GHC.Generic. Here is an incomplete example for a generic FC class definition (this is exactly what the generic-deriving package does):
First some extensions and importing the generics machinery
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeOperators #-}
import GHC.Generics
Then we define a class which mirrors your FC but we only have instances for the generic types
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.
Then you can have a default implementation for FC based on the above "generic" 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
And it works:
GHCI> t (==0) (Test (1 :: Int))
Test False
回答3:
As far as I know this is not possible, simply because there can be multiple constructors and it is unknown whether a generic constructor Foo can take any attribute as type.
Say for instance you have a type called:
data Foo a = Bar Int | Qux a
Now it means you cannot abstract away the constructor. As long as it is Qux, there is no problem, but Bar always expects an Int and thus will error. Since you here define an instance over any kind of c, there will be cases where this does not work. Note furthermore that the c in your instance declaration has nothing to do with the c in your definition of t. In other words: constructors can imply type constraints so you cannot simply factor them out.
A remark on your question is that you can generalize both you class defintion and 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
So you can remove the a in the class definition. This is not equivalent with your questions since here you say that it can work for any a. Whereas when you define a class FC c a you can decide for which as you want implement an instance.
来源:https://stackoverflow.com/questions/42026435/writing-a-generic-functor-instance-across-type-constructors