假设我有一个这样定义的参数类型:
newtype FancyComplex a b = FancyComplex (a, b)
除了数字参数之外,我打算永远不要将这个newtype用于任何其他参数。我的意思是,无论我可能做什么实现,我都知道参数a
和b
将始终是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
的一个方法,所以我们需要有它。我只能看到这些选项:
+
函数存储在FancyComplex
值内。这是可行的,但是Haskell报告要求这个newtype
在内存中具有相同的对表示。没有空间放置额外的指针。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
的成本。