在Haskell中,如何将约束附加到参数化的newtype,以便它们自动应用于使用它的任何类实例



假设我有一个这样定义的参数类型:

newtype FancyComplex a b = FancyComplex (a, b)

除了数字参数之外,我打算永远不要将这个newtype用于任何其他参数。我的意思是,无论我可能做什么实现,我都知道参数ab将始终是Num的一个实例。

我在这个问题中读到,你可以这样做:类型类约束可以在新类型定义中使用吗?

{-# LANGUAGE RankNTypes #-}
newtype (Num a, Num b) => FancyComplex a b = FancyComplex (a, b)

然而,这还不够。如果我写这样的类:

class StupidClass x where add :: x -> x -> x

然后我应该可以写

instance StupidClass (FancyComplex a b) where
add (FancyComplex (a, b)) (FancyComplex (a', b')) = FancyComplex (a+a', b+b')

但没有GHC会告诉我说我没有执行Num要求。所以我每次都被迫这么做:

instance (Num a, Num b) => StupidClass (FancyComplex a b) where
add (FancyComplex (a, b)) (FancyComplex (a', b')) = FancyComplex (a+a', b+b')

在newtype定义中编写约束所做的一切,就是迫使我每次都显式地编写约束。好吧,如果我忘了,这个仍然有用。但我当然希望不必每次都重写约束。

如何从newtype定义中自动隐式继承约束?这可能吗?如果没有,为什么不呢?

目前,我的薄弱解决方案是定义一个类型别名type FancyComplexReqs a b = (Num a, Num b)

感谢

至少在不改变newtype:的含义的情况下,这是无法实现的

newtype (Num a, Num b) => FancyComplex a b = FancyComplex (a, b)
instance StupidClass (FancyComplex a b) where
add (FancyComplex (a, b)) (FancyComplex (a', b')) = FancyComplex (a+a', b+b')

在最后一行中,a+a'需要函数+,这是Num的一个方法,所以我们需要有它。我只能看到这些选项:

  1. +函数存储在FancyComplex值内。这是可行的,但是Haskell报告要求这个newtype在内存中具有相同的对表示。没有空间放置额外的指针。

  2. Num a, Num b约束是隐式添加到实例定义中的,因为我们在实现中需要它。这是可行的,但明确一点不是更好吗?具有隐式约束会使实例更难读取,因为即使看起来没有约束,也有约束。

现在,有一个可能的替代方案:如果您想要选项1,并且您可以使用不同的运行时内存表示,那么请使用data

data FancyComplex a b where
FancyComplex :: (Num a, Num b) => a -> b -> FancyComplex a b

通过这种方式,每个值将存储其自己的指向Num实例的指针。这将需要更多的内存,但对于您的应用程序来说,这可能不是问题。

对GADT中的约束进行编码,如下所示:

{-# LANGUAGE GADTs #-}
data FancyComplex a b where
FancyComplex :: (Num a, Num b) => a -> b -> FancyComplex a b
class StupidClass x where add :: x -> x -> x
instance StupidClass (FancyComplex a b) where
add (FancyComplex a b) (FancyComplex a' b') = FancyComplex (a+a') (b+b')

您必须从newtype切换到data,因为约束会变成字典,而字典确实具有运行时表示。然而,通过这样做,我们可以去掉您的元组,这节省了相当于data的成本。

最新更新