在Haskell中,如何将约束附加到参数化的新类型,以便它们自动应用于使用它的任何类实例?
假设我有这样定义的参数化类型:在Haskell中,如何将约束附加到参数化的新类型,以便它们自动应用于使用它的任何类实例?,haskell,constraints,typeclass,newtype,Haskell,Constraints,Typeclass,Newtype,假设我有这样定义的参数化类型: {-# 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 (Fa
{-# 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 FancyComplex a b=FancyComplex(a,b)
除了数值型参数外,我不打算将此新类型用于任何其他参数。
我的意思是,对于我可能执行的任何实现,我知道参数a
和b
将始终是Num
的一个实例
我在这个问题中读到,你可以这样做:
{-#语言等级}
newtype(Num a,Num b)=>FancyComplex a b=FancyComplex(a,b)
然而,这还不够。
如果我写任何这样的类:
{-# 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')
class StupidClass x其中添加::x->x->x
那我就可以写了
实例StupidClass(FancyComplex a b),其中
添加(FancyComplex(a,b))(FancyComplex(a',b'))=FancyComplex(a+a',b+b'))
但是没有GHC会告诉我说我没有执行Num
要求。
所以每次我都被迫这么做:
instance(Num a,Num b)=>StupidClass(FancyComplex a b),其中
添加(FancyComplex(a,b))(FancyComplex(a',b'))=FancyComplex(a+a',b+b'))
在newtype定义中编写约束所做的一切就是强迫我每次都显式地编写约束。好的,这仍然很有用,以防我忘了。
当然,我不希望每次都重写约束
如何从newtype定义自动隐式继承约束?
这可能吗?若否,原因为何
目前,我的弱解决方法是定义一个类型别名typefancycomplexreqsab=(numa,numb)
谢谢在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
,因为约束会变成字典,字典中确实有运行时表示。但是,通过这样做,我们可以摆脱元组,它可以节省大量的数据成本。在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
,因为约束会变成字典,字典中确实有运行时表示。但是,通过这样做,我们可以摆脱元组,它可以节省大量的数据
成本。至少在不改变新类型的含义的情况下,这是无法实现的:
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
具有相同的内存表示形式。没有空间放置额外的指针
numa,numb
约束隐式添加到实例定义中,因为我们在实现中需要它。这是可行的,但是明确地说出来不是更好吗?具有隐式约束会使实例更难阅读,因为即使没有约束,也存在约束
现在,有一个可能的替代方案:如果您想要选项1,并且您可以使用不同的内存中运行时表示,那么可以使用数据:
data FancyComplex a b where
FancyComplex :: (Num a, Num b) => a -> b -> FancyComplex a b
这样,每个值都将存储自己的指向Num
实例的指针。它需要更多的内存,但对于您的应用程序来说,这可能不是问题。这无法实现,至少在不改变新类型的含义的情况下是如此:
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
具有相同的内存表示形式。没有空间放置额外的指针
numa,numb
约束隐式添加到实例定义中,因为我们在实现中需要它。这是可行的,但是明确地说出来不是更好吗?具有隐式约束会使实例更难阅读,因为即使没有约束,也存在约束
现在,有一个可能的替代方案:如果您想要选项1,并且您可以使用不同的内存中运行时表示,那么可以使用数据:
data FancyComplex a b where
FancyComplex :: (Num a, Num b) => a -> b -> FancyComplex a b
这样,每个值都将存储自己的指向Num
实例的指针。它将需要更多的内存,但对于您的应用程序来说,这可能不是问题。如果我没有记错的话,在newtype
中实际写入类型约束被认为是一种错误功能()。编写约束很有用,因为它出现在实例的签名中,因此也出现在文档中。但是,如果您不在newtype
级别限制您的类型,它将受到限制,因为在您的定义中使用了(+)
。但是,与实例本身的较弱和更混乱的要求相比,newtype要求可能更强大,表达也更优美。问题是,如果它是在newtype级别定义的,我看不出为什么它不能被自动继承,并在任何使用它的实例中记录下来。关键是,newtype约束可能相当复杂,编写时间也很长,这节省了空间。我的示例是人为构建的,以说明我的观点,但在我的代码中,我有更复杂的结构,每次重写约束都是一件痛苦的事情。你可以为每个实例编写最低要求,但这可能很难看。有时我宁愿写一个更严格、更统一的条件