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

时间:2017-02-03 14:24:52

标签: haskell types

我正在学习基本类型类,并为我的类型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

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

3 个答案:

答案 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声明中的instancec定义中的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