是否可以在Haskell中为已排序的二叉树树创建Functor实例?

时间:2013-11-30 05:31:38

标签: design-patterns haskell functional-programming functor

想象一下,我们将SortBinTree类型的构造函数定义为,例如,

data SortBinTree a = EmptyNode | Node a (SortBinTree a) (SortBinTree a);

仅当aOrd类型类的实例时才有意义,因此大多数函数在其声明的开头都有:: (Ord a) =>,尤其是用于创建此类树的函数名单。但是,要教Haskell,SortBinTreeFunctor类型类的实例,我们必须编写类似

的内容
instance Functor SortBinTree where
  fmap g tree = ...

这里的问题是我们必须处理g :: a->b,其中b不一定是Ord类型类的实例。这使得编写这样的函数成为问题,因为在创建SortBinTree b类型的元素时我们不能使用不等式。

这里有标准的解决方法吗?仅针对案例fmap定义b的任何方法都在Ord类型类中?

2 个答案:

答案 0 :(得分:8)

不,使用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

如果我们有很多不同类型的Functor s EqFunctorMonoidFunctor等等,这可能会很烦人。)但如果我们启用ConstraintKinds和{{ 3}},我们可以将它推广到受限制的仿函数类:

{-# 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

(通常情况下,你会看到有关受限制的monad的内容;这是相同的想法。)

或者,TypeFamilies,您可以编写自己的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),它可能违反了你的不变量 - 它向后排序.2所以要确保你的fmap保持不变。 Data.Set将这些内容分为as jozefg suggestedmap, which makes sure the invariants are preserved,。后一个函数具有简单的实现,即la notFmap,但如果给出不合作的函数,则可能产生无效的Set


¹mapMonotonic, which requires you to pass in an order-preserving function,但只有在fmap . const实施更快的情况下才会出现。

²但是,notFmap 来自 Hask 的子类别 fmap,其对象是具有Ord实例的类型,其态射是保留地图 Hask 的子类别,其对象为SortBinTree个带有Ord实例的类型。 (模拟一些关于“不合作”Eq / Ord实例的可能问题,例如那些混淆SetFunctor的实例。)

答案 1 :(得分:5)

有两种选择,如果你的类型满足仿函数法则那么正确的技巧是

{-# LANGUAGE DeriveFunctor #-}
data SortBinTree a = EmptyNode
                   | Node a (SortBinTree a) (SortBinTree a)
                   deriving Functor
-- Or a manual instance if you have some invariants that
-- need additional jiggering.

确保所有操作都需要Ord个实例。如果有人决定将树置于无用的状态,那么修复它就是他们自己的工作。

然而,为了这个工作,你必须满足仿函数法

 fmap id         === id
 fmap f . fmap g === fmap (f . g)

因此,如果您从树中删除重复项,那么您将进入trouble。这就是Data.Set作为Functor实例的原因可疑,它违反了这一规律。

如果你违反法律,那么你根本就不是一个探子。您无法向Haskell指定您只想处理Hask的子类别。在这种情况下,您应该只定义一个不同的函数

treeMap :: (Ord a, Ord b) => (a -> b) -> SortBinTree a -> SortBinTree b

在类别理论意义上,这仍然是一个算符,而不是Functor所讨论的那个。