跨类型构造函数编写泛型仿函数实例?

Writing a generic functor instance across type constructors?

我正在学习基本类型 类 并且已经为我的类型 Test a 编写了自己的 functor 实现(表现就像 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

换句话说,抽象出类型构造函数,替换为 cv,从而创建一个可以应用于具有上下文的任何值的通用实例?

据我所知,这是不可能的,因为可以有多个构造函数,并且不知道通用构造函数 Foo 是否可以将任何属性作为类型。

比如说你有一个类型叫做:

data Foo a = Bar Int | Qux a

现在这意味着你不能抽象出构造函数。只要是 Qux 就没有问题,但是 Bar 总是期待一个 Int 因此会出错。因为你在这里定义了一个 instance 在任何类型的 c 之上,所以在某些情况下这是行不通的。此外请注意,instance 声明中的 ct 定义中的 c 无关。换句话说:构造函数可以隐含类型约束,因此您不能简单地将它们分解出来。

关于你的问题的评论是你可以概括你的 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

因此您可以删除 class 定义中的 a。这不等同于您的问题,因为您在这里说它适用于任何 a。而当您定义 class FC c a 时,您可以决定要为哪些 a 实施 instance.

如您所知,c a 不是语法上有效的模式。但是,请将您的问题作为功能提案阅读:那将如何工作?并非每个 Functor 都有一个 single-element 构造函数,可以根据您的模式进行映射。一些例子:

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

无论如何,我认为 "generic instance" 的想法没有任何意义。该实例是您放置 type-specific 代码的地方。 (它必须去某个地方!)

如果您想在编写 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

您可以使用 GHC.Generic 做一些非常通用的事情。这是通用 FC class 定义的不完整示例(这正是 generic-deriving 包所做的):

首先进行一些扩展并导入泛型机制

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

import GHC.Generics

然后我们定义一个 class 来反映您的 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