在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约束可能相当复杂,编写时间也很长,这节省了空间。我的示例是人为构建的,以说明我的观点,但在我的代码中,我有更复杂的结构,每次重写约束都是一件痛苦的事情。你可以为每个实例编写最低要求,但这可能很难看。有时我宁愿写一个更严格、更统一的条件