如何在需要类型构造函数而不是具体类型的类的实例中确定类约束?

时间:2012-10-16 19:35:55

标签: haskell type-constructor

我目前位于Chapter 8Learn you a Haskell,我已经到达了Functor类型类的部分。在上述部分中,作者举例说明了如何使不同的类型成为类的实例(例如Maybe,自定义Tree类型等等。)看到这一点,我决定(为了好玩和练习)尝试实现Data.Set类型的实例;当然,在所有这些中忽略Data.Set.map

实际的实例本身很简单,我把它写成:

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

但是,因为我碰巧使用函数fromList,这会引入一个类约束,调用Set中使用的类型为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

请参阅:Live Example

我尝试在实例上添加约束,或者向fmap添加类型签名,但都没有成功(两者都是编译器错误。)

鉴于这种情况,如何实现约束并满足约束?有什么办法吗?

提前致谢! :)

3 个答案:

答案 0 :(得分:15)

不幸的是,没有使用标准Functor类轻松实现此目的。这就是Set默认情况下没有Functor实例的原因:你不能写一个。

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

我相信一种方法是使用constraint kinds重写Functor类来重新定义新Functor类可能具有的其他约束实例。这样您就可以指定Set必须包含Ord类中的类型。

另一种方法仅使用多参数类。我只能找到关于为Monad课程执行此操作的文章,但使Set Monad部分Functor面临与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 的一部分相同的问题。它被称为Restricted Monads

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

Set

基本上,你在这里所做的只是将中的类型作为类Functor的一部分。然后,这可以让您在编写该类的实例时约束这些类​​型的内容。

此版本的MultiParamTypeClasses需要两个扩展程序:FlexibleInstancesSet。 (您需要第一个扩展才能定义类,第二个扩展需要能够为{{1}}定义实例。)

Haskell : An example of a Foldable which is not a Functor (or not Traversable)?对此进行了很好的讨论。

答案 1 :(得分:2)

这是不可能的。 Functor课程的目的是,如果您有Functor f => f a,则可以将a替换为您喜欢的任何内容。不允许该类限制您仅返回此或那个。由于Set要求其元素满足某些约束(实际上这不是实现细节,但实际上是集合的基本属性),因此它不满足Functor的要求。

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

(另一个有趣的示例显示的是MonadPlus类。特别是,对于每个实例MonadPlus TC,您可以创建一个实例Monoid (TC a),但您不能总是去因此,Monoid (Maybe a)实例与MonadPlus Maybe实例不同,因为前者可以限制a,但后者不能。{/ p>

答案 2 :(得分:0)

You can do this using a CoYoneda Functor.

public string GetDiscount(int CustomerId)
        {
           //return something
        }