不能对使用 haskell 中的 GADT 定义的数据类型使用模式匹配

can't use pattern matching with datatypes defined using GADT in haskell

我正在尝试定义一个 Complex 数据类型,我希望构造函数能够将任何数字作为实例,所以我想使用泛型,只要它实现了Num 实例

我使用 GADTs 是为了这样做,因为据我了解 DataTypeContexts 语言扩展是一个“错误特征”,即使我认为这在这种情况下会很有用...

无论如何这是我的代码:

data Complex where
    Real            :: ( Num num, Show num ) => num -> Complex
    Imaginary       :: ( Num num, Show num ) => num -> Complex
    Complex         :: ( Num num, Show num ) => num -> num -> Complex


real :: ( Num num, Show num ) => Complex -> num
real (Real r)        = r
real (Imaginary _i ) = 0
real (Complex r _i ) = r

这里的 real 实现给出了以下错误:

Couldn't match expected type ‘num’ with actual type ‘num1’
  ‘num1’ is a rigid type variable bound by
    a pattern with constructor:
      Real :: forall num. (Num num, Show num) => num -> Complex,
    in an equation for ‘real’
    at <...>/Complex.hs:29:7-12
  ‘num’ is a rigid type variable bound by
    the type signature for:
      real :: forall num. (Num num, Show num) => Complex -> num
    at <...>/Complex.hs:28:1-47
• In the expression: r
  In an equation for ‘real’: real (Real r) = r
• Relevant bindings include
    r :: num1
      (bound at <...>/Complex.hs:29:12)
    real :: Complex -> num
      (bound at <...>/Complex.hs:29:1)

据我了解,这是由于 return 类型确实被解释为不同... 所以我尝试删除类型定义并让 ghc 对类型施展魔法,但结果类型签名是相同的...

任何人都可以向我解释这里有什么问题吗?

问题是,这些定义允许您在 (1) 构造 Complex 值和 (2) 应用 real 函数时选择不同的类型。这两种情况之间没有任何联系,因此没有什么可以强制它们之间的类型相同。例如:

c :: Complex
c = Real (42 :: Int)

d :: Double
d = real c

d的定义需要real函数来return一个Double,但是[=19=里面没有包裹Double ], 只有 Int.


至于解决方案,有两种可能:(1)在这两点之间建立联系,强制类型相同,(2)允许将里面的类型转换为任何其他数字类型.

要在两点之间建立 type-level 连接,我们需要使用在两点都存在的类型。那会是什么类型的?很明显,这就是 c 的类型。所以我们需要让 c 的类型以某种方式传达它包含的内容:

data Complex num = Real num | Imaginary num | Complex num num

real :: Complex num -> num
real = ...

-- Usage:
c :: Complex Int
c = Real 42

d :: Int
d = real c

请注意,我实际上不需要 GADT。


要允许类型转换,您需要为 num 类型要求另一种类型 class。 class Num 有办法将 转换为任何整数类型,但是没有办法将 转换为 任何这样的类型,因为它没有意义:3.1415 不能有意义地转换为整数类型。

所以你必须想出你自己的转换方式,并为所有允许的类型实现它:

class Convert a where
  toNum :: Num n => a -> n

data Complex where
    Real            :: ( Num num, Show num, Convert num ) => num -> Complex
    ...

real :: Num num => Complex -> num
real (Real r) = toNum r
...

明确一点,我认为第二种选择非常疯狂。我提供它只是为了说明。不要这样做。选择选项 1。