我正在学习基本类型类,并为我的类型functor
编写了我自己的Test a
实现(行为与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
是否可以实现以下内容:
instance FC c where
t f (c v) = c (f v)
错误:
Parse error in pattern: c
换句话说,抽象出类型构造函数,替换为c
和v
,从而创建一个可应用于具有上下文的任何值的通用实例?
答案 0 :(得分: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
个实例时花费较少的精力,可以使用GHC的DeriveFunctor
扩展名。
{-# 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
答案 1 :(得分: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.
然后,您可以根据上述" generic"以及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
答案 2 :(得分: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
实施instance
。