假设我有一个类型
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创造性地使用类型操作符。我接受第一个,但我喜欢两个。
答案 0 :(得分:10)
这可以通过一些类型技巧来实现,但是否值得它取决于你正在做什么(顺便提一下,你应该提供更多的上下文,以便我们可以帮助确定有多少类型的机器似乎值得使用)
{-# 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
(这可以减少,但我已经离开了“标签检查”和显式相等,以便进行更清晰的比较。)
您可以使用DataKinds
稍微进一步要求F
的第一个类型参数为AType
或BType
,但我不想添加太多额外的东西(这在评论中有点讨论)。
所有这一切,在很多情况下,@ DirkyJerky提供的Maybe
解决方案是要走的路(由于类型级操作的复杂性增加)。
有时这种类型级技术目前在Haskell中甚至不可能完全实现(它可能适用于您提供的示例,但它取决于它将如何使用),但您需要提供更多信息供我们确定。
答案 1 :(得分:4)
据我所知," Haskell方式"这样做不会引发运行时错误,而是返回Maybe F
将f
的签名更改为f :: F a -> F a -> Maybe (F a)
,
并添加另一个类型catch:
f (A x) B = Just $ A x
f B (A _) = Just $ B
f (A _) (A x) = Just $ A x
f B B = Nothing
编译器具有所有信息
没有编译器没有所有信息。
怎么样,如果你的程序用户决定输入f
函数的内容,他们选择了两个B
数据类型怎么办?那会发生什么?在编译程序后,您不能抛出编译错误。
答案 2 :(得分:2)
这是另一种类型系列的解决方案,您可能会发现它更简单,因为它只依赖于布尔逻辑。
$ locate libcrypto.so
/home/user/anaconda3/lib/libcrypto.so
/home/user/anaconda3/lib/libcrypto.so.1.0.0
/home/user/anaconda3/pkgs/openssl-1.0.2k-1/lib/libcrypto.so
/home/user/anaconda3/pkgs/openssl-1.0.2k-1/lib/libcrypto.so.1.0.0
/lib/x86_64-linux-gnu/libcrypto.so.1.0.0
/snap/core/2312/lib/x86_64-linux-gnu/libcrypto.so.1.0.0
/snap/core/2381/lib/x86_64-linux-gnu/libcrypto.so.1.0.0
/snap/core/2462/lib/x86_64-linux-gnu/libcrypto.so.1.0.0
/snap/electronic-wechat/7/lib/x86_64-linux-gnu/libcrypto.so.1.0.0
/usr/lib/x86_64-linux-gnu/libcrypto.so
*此案例不是必需的,但我将其包含在内是为了完整性。
现在这是一个类型错误:
{-# 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
的{{1}},反之亦然B
(“无法访问的代码”)的案例A
,甚至间接使用f B B