【问题标题】:How does one statisfy a class constraint in an instance of a class that requires a type constructor rather than a concrete type?如何在需要类型构造函数而不是具体类型的类实例中统计类约束?
【发布时间】:2012-10-16 19:35:55
【问题描述】:

我目前在Learn you a HaskellChapter 8 中,并且我已经到达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 添加类型签名,但均未成功(两者也是编译器错误。)

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

提前致谢! :)

【问题讨论】:

    标签: haskell type-constructor


    【解决方案1】:

    不幸的是,没有标准的Functor 类可以轻松地做到这一点。这就是为什么Set 默认不附带Functor 实例的原因:你不能写一个。

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

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

    另一种方法仅使用多参数类。我只能找到关于为Monad 类执行此操作的文章,但是将Set 设为Monad 的一部分与将其设为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
    

    基本上,您在这里所做的只是将类型 in 设为 Set 也是类的一部分。然后,当您编写该类的实例时,这可以让您限制这些类型可以是什么。

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

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

    【讨论】:

    • 约束函子类的一个问题是我们失去了很多权力。尤其是对于应用函子,我们经常想在其中放入函数。但是,在许多情况下,不可能为函数提供我们感兴趣的类的一般实例。
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      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]"
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-02-26
        • 1970-01-01
        • 2014-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多