在 Haskell 中,如何将约束附加到参数化新类型,以便它们自动应用于使用它的任何 class 实例?
In Haskell, how to attach constraints to a parametric newtype, so that they automatically apply to any class instance that uses it?
假设我有一个这样定义的参数类型:
newtype FancyComplex a b = FancyComplex (a, b)
我打算永远不会将此新类型用于数字参数以外的任何其他参数。
我的意思是,对于我可能执行的任何实现,我知道参数 a
和 b
将始终是 Num
的实例。
我在这个问题中读到你可以这样做:
{-# LANGUAGE RankNTypes #-}
newtype (Num a, Num b) => FancyComplex a b = FancyComplex (a, b)
然而这还不够。
如果我这样写任何 class:
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')
在新类型定义中编写约束所做的一切,就是迫使我每次都明确地编写约束。好的,这仍然有用,以防我忘记。
但是我当然希望不必每次都重写约束。
如何自动隐式继承新类型定义的约束?
这可能吗?如果不是,有什么不可以的原因吗?
目前我的弱解决方法是定义类型别名type FancyComplexReqs a b = (Num a, Num b)
谢谢
在 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
成本。
这个不能实现,至少不改变a的意思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
具有相同的内存对对表示形式。没有space追加指针
Num a, Num b
约束隐式添加到实例定义中,因为我们在实现中需要它。这可以工作,但明确说明不是更好吗?具有隐式约束会使实例更难阅读,因为即使看起来 none.
也存在约束
现在,有一个可能的替代方案:如果您想要选项 1,并且您可以使用不同的运行时内存表示,那么请改用 data
:
data FancyComplex a b where
FancyComplex :: (Num a, Num b) => a -> b -> FancyComplex a b
通过这种方式,每个值都将存储自己指向 Num
实例的指针。它将需要更多内存,但对于您的应用程序来说这可能不是问题。
假设我有一个这样定义的参数类型:
newtype FancyComplex a b = FancyComplex (a, b)
我打算永远不会将此新类型用于数字参数以外的任何其他参数。
我的意思是,对于我可能执行的任何实现,我知道参数 a
和 b
将始终是 Num
的实例。
我在这个问题中读到你可以这样做:
{-# LANGUAGE RankNTypes #-}
newtype (Num a, Num b) => FancyComplex a b = FancyComplex (a, b)
然而这还不够。 如果我这样写任何 class:
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')
在新类型定义中编写约束所做的一切,就是迫使我每次都明确地编写约束。好的,这仍然有用,以防我忘记。 但是我当然希望不必每次都重写约束。
如何自动隐式继承新类型定义的约束? 这可能吗?如果不是,有什么不可以的原因吗?
目前我的弱解决方法是定义类型别名type FancyComplexReqs a b = (Num a, Num b)
谢谢
在 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
成本。
这个不能实现,至少不改变a的意思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
具有相同的内存对对表示形式。没有space追加指针Num a, Num b
约束隐式添加到实例定义中,因为我们在实现中需要它。这可以工作,但明确说明不是更好吗?具有隐式约束会使实例更难阅读,因为即使看起来 none. 也存在约束
现在,有一个可能的替代方案:如果您想要选项 1,并且您可以使用不同的运行时内存表示,那么请改用 data
:
data FancyComplex a b where
FancyComplex :: (Num a, Num b) => a -> b -> FancyComplex a b
通过这种方式,每个值都将存储自己指向 Num
实例的指针。它将需要更多内存,但对于您的应用程序来说这可能不是问题。