16

我目前在Learn you a Haskell的第 8 章,我已经到达了关于typeclass 的部分。在上述部分中,作者给出了如何使不同类型成为类实例的示例(例如,自定义类型等)。看到这一点,我决定(为了好玩和练习)尝试为该类型实现一个实例。当然,所有这些都忽略了。FunctorMaybeTreeData.SetData.Set.map

实际实例本身非常简单,我将其写为:

instance Functor Set.Set where
  fmap f empty = Set.empty
  fmap f s = Set.fromList $ map f (Set.elems s)  

但是,由于我碰巧使用了这个函数,fromList这会引入一个类约束,调用Setto be中使用的类型Ord,正如编译器错误所解释的那样:

Error occurred
ERROR line 4 - Cannot justify constraints in instance member binding
*** Expression    : fmap
*** Type          : Functor Set => (a -> b) -> Set a -> Set b
*** Given context : Functor Set
*** Constraints   : Ord b

请参阅:实时示例

我尝试对实例施加约束,或向 中添加类型签名fmap,但均未成功(两者也是编译器错误。)

在这种情况下,如何实现和满足约束?有没有可能的方法?

提前致谢!:)

4

3 回答 3

15

不幸的是,标准类没有简单的方法来做到这一点。Functor这就是为什么默认情况下Set不附带Functor实例的原因:您不能编写实例。

这是一个问题,并且有一些建议的解决方案(例如Functor以不同的方式定义类),但我不知道是否就如何最好地处理这个问题达成共识。

Functor我相信一种方法是使用约束类型重写类,以具体化新类实例Functor可能具有的附加约束。这将让您指定Set必须包含类中的Ord类型。

另一种方法仅使用多参数类。我只能找到关于在Monad课堂上执行此操作的文章,但制作SetpartMonad与制作Functor. 它被称为Restricted Monads

在这里使用多参数类的基本要点似乎是这样的:

class Functor' f a b where
  fmap' :: (a -> b) -> f a -> f b

instance (Ord a, Ord b) => Functor' Data.Set.Set a b where
  fmap' = Data.Set.map

本质上,您在这里所做的只是在类中创建Set类型。然后,当您编写该类的实例时,这可以让您限制这些类型可以是什么。

此版本Functor需要两个扩展名:MultiParamTypeClassesFlexibleInstances. (您需要第一个扩展才能定义类,第二个扩展才能定义实例Set。)

Haskell:不是 Functor(或不可遍历)的 Foldable 示例?对此有很好的讨论。

于 2012-10-16T19:38:53.607 回答
2

这是不可能的。这门课的目的Functor是,如果你有Functor f => f a,你可以用a你喜欢的任何东西替换 。该课程不允许限制您只返回这个或那个。由于Set要求它的元素满足某些约束(实际上这不是实现细节,而是集合的基本属性),它不满足Functor.

正如另一个答案中提到的那样,开发这样一个类的方法Functor确实会以这种方式限制你,但它确实是一个不同的类,因为它为类的用户提供了更少的保证(你不能将它与任何你想要的类型参数),以换取适用于更广泛的类型。也就是说,毕竟,定义类型属性的经典权衡:您想要满足的类型越多,它们必须被迫满足的越少。

(另一个有趣的例子是MonadPlus类。特别是,对于每个实例,MonadPlus TC 您都可以创建一个实例Monoid (TC a),但您不能总是反过来。因此,Monoid (Maybe a)实例与实例不同MonadPlus Maybe,因为前者可以限制a但后者不能。)

于 2012-10-25T15:18:51.587 回答
0

You can do this using a CoYoneda Functor.

{-# LANGUAGE GADTs #-}

data CYSet a where
    CYSet :: (Ord a) => Set.Set a -> (a -> b) -> CYSet b

liftCYSet :: (Ord a) => Set.Set a -> CYSet a
liftCYSet s = CYSet s id

lowerCYSet :: (Ord a) => CYSet a -> Set.Set a
lowerCYSet (CYSet s f) = Set.fromList $ map f $ Set.elems s

instance Functor CYSet where
  fmap f (CYSet s g) = CYSet s (f . g)

main = putStrLn . show
  $ lowerCYSet
  $ fmap (\x -> x `mod` 3)
  $ fmap abs
  $ fmap (\x -> x - 5)
  $ liftCYSet $ Set.fromList [1..10]
-- prints "fromList [0,1,2]"
于 2017-11-15T19:03:33.710 回答