如何在 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
类型构造函数添加了一个额外的参数。这是一个类型索引(任何地方都没有该类型的值,它只是一个类型级别标记)。我创建了两种不同的空类型(AType
和 BType
)用作 F
.
的幻像类型参数
类型族 ValidCombo
在类型级别充当函数(请注意,定义与典型的 Haskell 值级别函数的定义方式非常相似,但使用类型而不是值)。 ()
是一个永远不会导致类型错误的空约束(因为空约束总是很简单地得到满足)。在类型级别,a ~ b
约束 a
和 b
为同一类型(~
是类型级别的相等),如果它们不相同则会报错类型。它大致类似于看起来像这样的值级别代码(使用您原来的 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
的第一个类型参数是 AType
或 BType
,但我不想添加太多额外的东西(这在评论中讨论了一下)。
综上所述,在许多情况下,@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
假设我有一个类型
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
类型构造函数添加了一个额外的参数。这是一个类型索引(任何地方都没有该类型的值,它只是一个类型级别标记)。我创建了两种不同的空类型(AType
和 BType
)用作 F
.
类型族 ValidCombo
在类型级别充当函数(请注意,定义与典型的 Haskell 值级别函数的定义方式非常相似,但使用类型而不是值)。 ()
是一个永远不会导致类型错误的空约束(因为空约束总是很简单地得到满足)。在类型级别,a ~ b
约束 a
和 b
为同一类型(~
是类型级别的相等),如果它们不相同则会报错类型。它大致类似于看起来像这样的值级别代码(使用您原来的 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
的第一个类型参数是 AType
或 BType
,但我不想添加太多额外的东西(这在评论中讨论了一下)。
综上所述,在许多情况下,@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