Haskell创建带有类型约束的函数



我有这个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限制为任何类型v,这是Eq的实例,并使LatticePoint成为Functor的实例。

您的LatticePoint不是Functor。一个Functor被定义为声明"对于每一对类型ab,我可以将函数a -> b提升为函数f a -> f b"。您不能提供只适用于少数类型的实例,也不能提供只适用于某些类型而不是全部类型的实例。这与Set不是函子的原因相同,因为对其进行的所有重要操作都需要对元素类型进行Ord操作。

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

这是因为,如果您直接约束数据类型,对于每个aLatticePoint a甚至没有意义,这使得对类型进行推理变得更加困难(每次您想在任何上下文中提到您的类型时,您都需要有效地伴随着正确性证明)。但是,如果LatticePoint a类型对于每个a都是定义良好的,但恰好对其中一些是无用的且不可构造的,则更容易推断我们的类型。所以我假设我们的声明是

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

我们可以用MonoFunctor近似你想要的。MonoFunctor提供了一个弱得多的约束。它说"给定容器的元素类型的定义,我可以将元素上的函数提升到容器类型上的函数"。重要的是,我们从来没有说过它必须适用于所有类型,只适用于容器说有效的"元素"类型。

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的一个限制,我不知道有任何类型类允许MonoFunctor风格的类约束允许非自同态的映射。

将我的注释转换为答案,您通常将约束推迟到在值需要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

最新更新