Haskell 创建具有类型约束的仿函数

Haskell create functor with type constraints

我有这个Haskell代码片段:

{-# LANGUAGE InstanceSigs #-}

module LatticePoint 
where

import Data.List    

data (Eq v) => LatticePoint v = LatticePoint{prob::Double, value::v}

instance Functor LatticePoint where
    fmap :: (Eq a, Eq b) => (a -> b) -> LatticePoint a -> LatticePoint b 
    fmap f lp = LatticePoint {prob = prob lp, value = f $ value lp}

编译时出现以下错误,我不明白

src/LatticePoint.hs:12:14: error:
    • No instance for (Eq a)
      Possible fix:
        add (Eq a) to the context of
          the type signature for:
            fmap :: forall a b. (a -> b) -> LatticePoint a -> LatticePoint b
    • When checking that instance signature for ‘fmap’
        is more general than its signature in the class
        Instance sig: forall a b.
                      (Eq a, Eq b) =>
                      (a -> b) -> LatticePoint a -> LatticePoint b
           Class sig: forall a b.
                      (a -> b) -> LatticePoint a -> LatticePoint b
      In the instance declaration for ‘Functor LatticePoint’
   |
12 |     fmap ::  (Eq a, Eq b) => (a -> b) -> LatticePoint a -> LatticePoint b 
   | 

         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

我想要实现的是将 LatticePoint 限制为作为 Eq 实例的任何类型 v,并使 LatticePoint 成为 Functor 的实例。

您的 LatticePoint 不是 Functor. A Functor is defined to state "For every pair of types a and b, I can lift a function a -> b to a function f a -> f b". You can't provide an instance that only works for a few types, or for some but not all. This is the same reason Set 不是函子,因为对它的所有重要操作都需要 Ord 元素类型。

在我们可以使用替代 Functor 之前,我们实际上需要从这个 post 的评论部分获取一些建议。正如所指出的,在 Haskell 中,通常不约束数据类型本身,而仅约束作用于它的所有函数(允许数据类型约束的特性在很大程度上被认为是一种错误特性,不鼓励使用它)。因此,与其将 Eq 约束放在 data 行中,不如将 data 行保留为普通声明并约束每个采用或产生 LatticePoint 的函数更为惯用。

这是因为,如果您直接限制数据类型,甚至 对每个 aLatticePoint a 都没有意义 ,这使得它变得更加困难对类型进行推理(每次您想在任何上下文中提及您的类型时,您都需要有效地证明其正确性)。但是如果 LatticePoint a 类型对于每个 a 都是 well-defined 但恰好对其中一些无用且不可构造,那么推理就容易多了关于我们的类型。所以我假设我们的声明是

data LatticePoint v = LatticePoint{prob::Double, value::v}

我们可以用 MonoFunctor 来近似您想要的。 MonoFunctor 提供了一个弱得多的约束。它说“给定我们容器的 'element type' 的一些定义,我可以将元素上的函数提升为容器类型上的函数”。至关重要的是,我们从未说过它必须适用于 所有 类型,仅适用于容器认为是有效“元素”的类型。

type instance Element (LatticePoint a) = a

现在我们可以编写 MonoFunctor 实例了。

instance Eq a => MonoFunctor (LatticePoint a) where
  omap :: (a -> a) -> LatticePoint a -> LatticePoint a
  omap f latticePoint = ...

您会在这里注意到一件事:我们的函数必须从 a 映射到 a。它不能映射到不同的目标类型,即使源和目标都是 Eq。这只是 MonoFunctor 的限制,我不知道有任何类型 class 允许 MonoFunctor 样式 class 约束 允许不是自同态的映射。

将我的评论转换为答案,您通常会在值需要 Eq 的上下文中推迟对 使用 LatticePoint 的函数的约束约束。这使您可以定义 fmap.

module LatticePoint 
where

import Data.List    

data LatticePoint v = LatticePoint{prob::Double, value::v}

instance Functor LatticePoint where
    fmap :: (a -> b) -> LatticePoint a -> LatticePoint b 
    fmap f lp = LatticePoint {prob = prob lp, value = f $ value lp}

foo :: Eq a => LatticePoint a -> Whatever
foo lp = ...

仔细想想,数据结构本身并不关心value是否可以比较相等,只关心使用的函数数据结构。

作为具体示例,考虑 (==) 本身的定义:

instance Eq a => Eq (LatticePoint a) where
    (LatticePoint p1 v1) == (LatticePoint p2 v2) = p1 == p2 && v1 == v2