如何在 Haskell 中触发类型错误?

How can I trigger a type error in Haskell?

假设我有一个类型

data F a = A a | B

我这样实现函数 f :: F a -> F a -> F a

f (A x) B = A x
f B (A _) = B
f (A _) (A x) = A x

然而没有f B B这在逻辑上是不可能的,所以我想要:

f B B = GENERATE_HASKELL_COMPILE_ERROR

这当然不起作用。省略定义或使用 f B B = undefined 不是解决方案,因为它会生成运行时异常。我想得到一个编译时类型错误。

编译器有所有的信息,应该能推断出我犯了逻辑错误。如果说我声明 let z = f (f B (A 1)) B 应该是立即编译时错误,而不是一些可以隐藏在我的代码中多年的运行时异常。

我找到了一些关于合同的信息,但我不知道如何在这里使用它们,我很好奇在 Haskell.

中是否有任何标准的方法可以做到这一点

编辑:事实证明,我试图做的是所谓的依赖类型,Haskell(目前)还没有完全支持它。然而,使用索引类型和几个扩展可能会产生类型错误。 David Young 的解决方案似乎更直接,而 Jon Purdy 则创造性地使用类型运算符。我接受第一个,但我都喜欢。

据我所知,这样做的 "Haskell way" 不会抛出运行时错误,而是返回 Maybe F

f 的签名更改为 f :: F a -> F a -> Maybe (F a), 并添加另一种类型的捕获:

f (A x) B = Just $ A x
f B (A _) = Just $ B
f (A _) (A x) = Just $ A x
f B B = Nothing

The compiler has all the information

不,编译器没有所有信息。

如果您的程序的用户决定向 f 函数输入什么,并且他们选择了两种 B 数据类型,那会怎么样?那会发生什么?程序编译后不能抛出编译错误。

这可以通过一些类型技巧来实现,但它是否值得取决于你在做什么(顺便说一句,你应该提供更多的上下文,这样我们可以帮助确定多少类型机器看起来是值得的使用)。

{-# LANGUAGE GADTs           #-}
{-# LANGUAGE TypeFamilies    #-}
{-# LANGUAGE DataKinds       #-}
{-# LANGUAGE ConstraintKinds #-}

import Data.Constraint

data AType
data BType

data F x y where
  A :: a -> F AType a
  B ::      F BType a

type family ValidCombo x y :: Constraint where
  ValidCombo BType ty2 = ty2 ~ AType
  ValidCombo ty1   ty2 = ()

f :: ValidCombo ty1 ty2 => F ty1 a -> F ty2 a -> F ty1 a
f (A x) B     = A x
f B     (A _) = B
f (A _) (A x) = A x

在编译时,既不可能定义f B B = ...,也不可能尝试像f B B那样调用它。您的示例 let z = f (f B (A 1)) B 不会进行类型检查(尽管更复杂的示例可能 运行 出现问题)。

所做的第一件事是我向 F 类型构造函数添加了一个额外的参数。这是一个类型索引(任何地方都没有该类型的值,它只是一个类型级别标记)。我创建了两种不同的空类型(ATypeBType)用作 F.

的幻像类型参数

类型族 ValidCombo 在类型级别充当函数(请注意,定义与典型的 Haskell 值级别函数的定义方式非常相似,但使用类型而不是值)。 () 是一个永远不会导致类型错误的空约束(因为空约束总是很简单地得到满足)。在类型级别,a ~ b 约束 ab 为同一类型(~ 是类型级别的相等),如果它们不相同则会报错类型。它大致类似于看起来像这样的值级别代码(使用您原来的 F 类型),但在类型级别:

data Tag = ATag | BTag
  deriving Eq

getTag :: F a -> Tag
getTag (A _) = ATag
getTag B     = BTag

validCombo :: F a -> F a -> Bool
validCombo B tag2 = (getTag tag2) == ATag
validCombo _ _    = True

(这可以减少,但为了更清楚的比较,我保留了 "tag checking" 和显式相等。)

你可以更进一步地使用 DataKinds 来要求 F 的第一个类型参数是 ATypeBType,但我不想添加太多额外的东西(这在评论中讨论了一下)。

综上所述,在许多情况下,@DirkyJerky 提供的 Maybe 解决方案是可行的方法(由于类型级操作的复杂性增加)。

有时这种类型级别的技术目前在 Haskell 中甚至不完全可行(它可能适用于您提供的示例,但这取决于它将如何使用),但您需要提供更多信息以便我们确定。

这是另一个类型族的解决方案,您可能会发现它更简单,因为它只依赖于布尔逻辑。

{-# LANGUAGE DataKinds, GADTs, TypeFamilies, TypeOperators #-}

data F x a where
  A :: a -> F 'True a
  B :: F 'False a

f
  :: ((x || y) ~ 'True)
  => F x a
  -> F y a
  -> F (x || Not y) a

f (A a) B = A a
f B (A _) = B
f (A _) (A a) = A a

----

type family Not a where
  Not 'True = 'False
  Not 'False = 'True

type family a || b where
  'True || 'True = 'True  -- *
  'True || b = 'True
  a || 'True = 'True
  a || b = 'False

* 这种情况不是必需的,但为了完整起见,我将其包括在内。

现在是类型错误:

  • Return B 其中 A 是预期的,反之亦然
  • 包括 f B B(“无法访问的代码”)的大小写
  • 调用 f B B,甚至可以间接调用 f (f B (A 1)) B