使数据类型成为 Functor 的实例以映射到参数类型的字段

Making a datatype an instance of Functor to map on a field which is of parametric type

上跟进关于 学习 Haskell 的好事

作者在Chapter 8末尾声明了这个数据类型(略作简化,希望没问题)

data Barry t k p = BarryV p (t k) deriving (Show)

然后使它成为 Functor

的一个实例
instance Functor (Barry a b) where
  fmap f (BarryV x y) = BarryV (f x) y

然后总结

There we go! We just mapped the f over the first field.

是的。首先。所以我的问题是:如果我想映射第二个字段怎么办?

实际上第二个字​​段不能是IntCharFloat等简单的类型;它必须是可以作为应用于具体类型的类型构造函数获得的类型(斜体文本与“参数类型”相同,对吧? 不是,是参数化类型),如Just 3Right "hello""hello"[1..10]、很快;因此映射到 第二个字段 和映射到 第二个字段的内容 似乎不同。

我真的很困惑,但我想最后一段已经足够努力了。

最简单的方法是将您的类型创建为具体类型的函数:

mymap :: (t1 k1 -> t2 k2) -> Barry t1 k1 p -> Barry t2 k2 p
mymap f (BarryV x y) = BarryV x (f y)

如果你真的想用现有的类型类来做,你可能可以通过包装类型(如 ProductCompose 将足够多的转换链接在一起,以获得类似 Bifunctor 的东西弹出,但我认为在这种情况下不值得这样做。

Functor 类型 class 过于笼统,无法在第二个字段的类型 t k 上应用映射,但 可以 在第二个字段的类型 k 内应用映射。因此,使用您问题中的术语,我们不能使用 Functor 来映射 t k 类型的第二个字段,但我们可以使用它来映射 k 类型的内容在类型为 t k 的第二个字段中(前提是 t 是一种允许映射其内容的结构)。

关于尝试使用 Functor 映射类型 t k,问题在于它允许违反 Barry 类型定义的转换。以下函数:

censor :: (Functor f) => f a -> f ()
censor = (() <$)

应该适用于任何仿函数实例,将目标类型 a 的字段替换为单元 ()。例如:

> censor (Just 5)
Just ()
> censor [1..5]
[(),(),(),(),()]

如果 Barry 是其第二个字段的 t k 类型的函子,那么我将能够获取有效的 Barry 值:

> let myBarry = BarryV 10 "hello" :: Barry [] Char Int

并对其应用 censor 以审查其第二个字段:

> censor myBarry
BarryV 10 ()

但是这个值的类型是什么?对于某些 tk 显然 Barry t k Int 这样 t k = (),但这是不可能的。无法将 "split" 类型 () 分为两部分 tk。因此,BarryV 10 () 不是有效 Barry 类型的值,它的存在意味着我们在程序中构造了一个无效的 Barry 类型。

另一方面,我们可以k参数中为Barry创建一个Functor实例。我们不能直接这样做,因为 Haskell 语法只允许我们为以其 "last" 参数为目标的类型表达式定义 Functor 个实例。所以 Barry t k p 可以通过为 Barry t k 定义一个 Functor 实例在最后一个参数 p 中成为 Functor,但它不能成为 Functor在中间参数k.

如果我们有一个参数顺序不同的变体:

data Larry p t k = LarryV p (t k) deriving (Show)

然后我们可以定义 Functor 实例:

instance Functor (Larry p t) where
  fmap f (LarryV p tk) = LarryV p (fmap f tk)

这给出了一个类型错误,说 t 没有 Functor 实例,但是如果我们限制自己只在有 Functor t 时定义这个实例,它工作正常:

instance Functor t => Functor (Larry p t) where
  fmap f (LarryV p tk) = LarryV p (fmap f tk)

现在,只要 t 是一个 Functor,我们就有 Larry p tFunctor。例如:

> let myLarry = LarryV 10 "hello"
> :t myLarry
myLarry :: Num p => Larry p [] Char
> import Data.Char
> fmap toUpper myLarry
LarryV 10 "HELLO"

这是可行的,因为 t = [] 是一个 Functor,所以我们得到了我们需要的实例。

请注意,在实际代码中,不是引入新类型 Larry,而是在 "middle" 参数中定义 Functor 实例的标准方法是使用 newtype 包装器,类似于:

newtype Barry' p t k = Barry' (Barry t k p)
instance Functor t => Functor (Barry' p t) where
  fmap f (Barry' (BarryV p tk)) = Barry' (BarryV p (fmap f tk))