不,Functor 类型类无法做到这一点。如您所述,the Prelude gives us¹
class Functor f where
fmap :: (a -> b) -> f a -> f b
它无法对b 施加限制。我们可以定义一个OrdFunctor 类:
class OrdFunctor f where
fmapOrd :: (Ord a, Ord b) => (a -> b) -> f a -> f b
如果我们有很多不同类型的Functors(EqFunctor、MonoidFunctor 等),这可能会很烦人,但是如果我们打开 ConstraintKinds 和 TypeFamilies,我们可以将其概括为受限仿函数类:
{-# LANGUAGE ConstraintKinds, TypeFamilies #-}
import GHC.Exts (Constraint)
import Data.Set (Set)
import qualified Data.Set as S
class RFunctor f where
type RFunctorConstraint f :: * -> Constraint
fmapR :: (RFunctorConstraint f a, RFunctorConstraint f b) => (a -> b) -> f a -> f b
-- Modulo the issues with unusual `Eq` and `Ord` instances, we might have
instance RFunctor Set where
type RFunctorConstraint f = Ord
fmapR = S.map
(通常,您会看到有关受限单子的内容;这是相同的想法。)
或者,as jozefg suggested,您可以编写自己的 treeMap 函数而不将其放入类型类中。没有错。
但是请注意,在将 SortBinTree 设为仿函数时应该小心;以下是不是fmap。 (不过,deriving (..., Functor) 会产生它,所以不要使用它。)
notFmap :: (Ord a, Ord b) => (a -> b) -> SortBinTree a -> SortBinTree b
notFmap f EmptyNode = EmptyNode
notFmap f (Node x l r) = Node (f x) (notFmap l) (notFmap r)
为什么不呢?考虑notFmap negate (Node 2 (Node 1 EmptyNode EmptyNode) EmptyNode)。这将产生树Node (-2) (Node (-1) EmptyNode EmptyNode) EmptyNode),这可能违反了你的不变量——它是向后排序的。²所以确保你的fmap是保持不变的。 Data.Set 将这些拆分为 map, which makes sure the invariants are preserved, 和 mapMonotonic, which requires you to pass in an order-preserving function。后一个函数的实现很简单,就像notFmap,但如果给定不合作的函数,可能会产生无效的Sets。
¹The Data.Functor module also exposes the (<$) :: Functor f => a -> f b -> a type class method,但这只是为了防止fmap . const 有更快的实现。
² 但是,notFmap 是 fmap 来自 Hask 的子类别,其对象是具有 Ord 实例的类型,并且其态射是 order -preserving 映射 到 Hask 的子类别,其对象是 SortBinTrees 超过具有 Ord 实例的类型。 (模数关于“不合作”Eq/Ord 实例的一些潜在担忧,例如那些将Set 混淆为Functor 的实例。)