简短的不回答
在你自己做任何思考之前,试着让 GHC 为你编写实例:
{-# LANGUAGE DeriveFunctor #-}
data Tree a = Leaf | Branch a (Tree a) (Tree a)
deriving (Functor)
这恰好在这种情况下有效,然后保证您有一个守法的实例!
说真的,这是您通常应该为您的数据类型获取Functor 实例的方式。但是你仍然应该在有意义的时候了解自己!
实际答案
我想确定,不只是针对这种数据类型,还针对String 等其他数据类型,这些数据类型是否是Functor 的守法实例
因此,对于Functor 实例,您首先需要一个参数类型,即一个“容器”,它不关心您在其中存储什么类型。所以,严格来说,函子根本不应该是一个类型,而是一个类型构造函数或类型级函数★。实际上,您可以通过检查数据声明是否具有类型变量来看到这一点:如果是 Tree,您会立即在代码中看到它
data Tree <b>a</b> = ... ✓
如果手头没有源代码,可以向 GHCi 索要种类:
Prelude> :set -XTypeInType -XNoStarIsType†
Prelude> :k Maybe
Maybe :: Type -> Type ✓
Prelude> :k String
String :: Type ✗
如您所见,String 甚至没有类型参数,所以它不可能是函子。‡
接下来,你需要看看类型变量是如何在数据结构中使用的。如果有多个类型参数,则以下所有内容都适用于它们的最后一个(最右边),例如在data Either a b = ... 中,我们将讨论b 参数。
-
如果根本不使用它(即,如果它是 phantom 类型的参数),那么您可以简单地编写一个守法的Functor 实例:也不要使用映射函数。
data Tough a = Tough String
instance Functor Tough where
fmap _ (Tough s) = Tough s
(但是,在这种情况下,您可能不应该编写仿函数实例,因为幻像参数通常是常量唯一标签。)
-
如果它直接用作类型构造函数中某个字段的一部分,那么您可以编写一个仿函数实例。然后应该将fmap-ped 函数应用于所有这些值。
data Maybe a = Nothing<br> | Just <b>a</b><br><br>instance Functor Maybe where<br> fmap _ Nothing = Nothing<br> fmap f (Just a) = Just $ <b>f a</b>
-
如果它用在数据结构中嵌套更深的地方,但所有嵌套都在函子本身中,那么它就是函子。 (如果它是你试图定义自己的同一个函子,这也成立,即对于递归类型!)
data TwoLists a = TwoLists {listL :: [a], listR :: [a]}<br><br>instance Functor TwoLists where<br> fmap f (TwoLists ll lr) = TwoLists (fmap f ll) (fmap f lr)
-
[高级,如果你暂时忽略这个可能最好]如果嵌套不是由普通(协变)函子组成,而是由 偶数个逆变函子组成,那么你的整个类型也是协变函子。
★类型构造函数实际上是一种非常特殊的类型级函数,特别是它们是单射的。从数学上讲,仿函数不需要单射地映射其对象(在 Hask 的情况下,对象是类型),但在 Haskell 中,这对于类型检查器来说是必需的。
†这些句法扩展导致 GHCi 将类型显示为Type;从历史上看,它会显示*,这仍然是旧 GHC 版本中的默认设置,但现在已弃用。
‡不过,String 实际上是[Char] 的同义词,即字符列表,而列表是函子。所以你可以实际上在一个字符串上执行fmap,但这并不意味着你使用“字符串仿函数”:你正在使用列表仿函数,甚至如果你从一个字符串开始,结果可能不是一个字符串(而是一个整数列表)。