我是Haskell的新手。抱歉,这个问题的答案很明显。
我有
data Tmp = Foo Int
| Bar Int
| Baz Int
和
data Test = A Tmp Tmp
构造函数A Tmp Tmp
可以采用任何构造函数,Tmp
除了A (Baz i) (Baz j)
wherei
和j
are any Int
。有没有什么办法可以限制第二Tmp
中A Tmp Tmp
被Baz
当第一Tmp
已经是Baz
?
答案取决于您希望如何实施限制:在运行时或编译时。
要在运行时实施限制,您可以添加一个函数(例如makeA
)来检查限制,然后调用构造函数。这样的函数会做一些事情,然后调用构造函数,也称为智能构造函数。如果仅从模块导出智能构造函数makeA
而不是实际构造函数A
,则可以确保其他模块使用智能构造函数,因此始终检查限制。
例:
module Test (Tmp (Foo, Bar, Baz), Test (), makeA) where
data Tmp
= Foo Int
| Bar Int
| Baz Int
data Test = A Tmp Tmp
makeA :: Tmp -> Tmp -> Tmp
makeA (Baz _) (Baz _) = error "makeA: two baz problem"
makeA tmp1 tmp2 = A tmp1 tmp2
这种技术的好处是您根本不必更改数据类型。缺点是该限制仅在运行时强制执行。
要在编译时强制执行此限制,您需要以某种方式更改数据类型。当前数据类型的问题在于类型检查器无法区分由Foo
和Bar
构造的值以及由构造的值Baz
。对于类型检查器来说,这些都是Tmp
值,因此类型检查器无法强制某些Tmp
值可以,而其他值则不能。因此,我们必须更改数据类型以对类型中的Tmp
值的“ Bazness”进行编码。
将Bazness编码为该类型的一种方法是Tmp
按照以下方式进行重组:
data TmpNotBaz
= Foo Int
| Bar Int
data Tmp
= NotBaz TmpNotBaz
| Baz Int
现在很明显,类型的值TmpNotBaz
不能为Baz
,但是类型的值Tmp
可以为Baz
。这种想法的好处是它仅使用基本的Haskell功能。一个次要的缺点是您需要将对的调用NotBaz
放入代码中。一个主要的缺点是我们仍然不能直接表达“如果一个参数A
可以是,Baz
则可以是其中一个”的想法。我们将不得不编写多个版本的A
:
data Test
= A1 TmpNotBaz Tmp
| A2 Tmp TmpNotBaz
现在,我们可以通过选择A1
或A2
根据需要来表达我们想要的所有值,而A (Baz ...) (Baz ...)
不再能够根据需要来表达。该解决方案的一个问题是,曾经有多种表示形式,例如A (Foo 1) (Foo 2)
:A1 (Foo 1) (NotBaz (Foo 2))
和A2 (NotBaz (Foo 1)) (Foo 2)
表示该值。
您可以尝试像这样处理数据类型的结构,并创建适合您情况的版本。
用于对类型中的Bazness进行编码的另一种方法是在类型上注释一些类型级别的信息,Tmp
并使用类型级别的编程来对此类型级别的信息进行推理。这个想法的缺点是它使用了更高级的Haskell功能。实际上,有许多新兴的方法可以完成这种事情,但尚不清楚哪种方法最终将被视为“标准”高级Haskell。也就是说,这是一种方法:
{-# LANGUAGE GADTs, TypeFamilies, DataKinds #-}
data Bazness = IsBaz | NotBaz
data BothBazOrNot = BothBaz | NotBothBaz
type family AreBothBaz (b1 :: Bazness) (b2 :: Bazness) :: BothBazOrNot where
AreBothBaz 'IsBaz 'IsBaz = 'BothBaz
AreBothBaz _ _ = 'NotBothBaz
data Tmp (b :: Bazness) :: * where
Foo :: Int -> Tmp 'NotBaz
Bar :: Int -> Tmp 'NotBaz
Baz :: Int -> Tmp 'IsBaz
data Test where
A :: AreBothBaz b1 b2 ~ 'NotBothBaz => Tmp b1 -> Tmp b2 -> Test
注意构造函数的类型签名Foo
,Bar
并Baz
讨论构造函数是创建IsBaz
还是构造某些东西NotBaz
。而如何为类型签名A
的一些谈判b1
和b2
选择,使NotBothBaz
。
使用此代码,我们可以编写以下表达式:
A (Foo 1) (Bar 2)
A (Foo 1) (Baz 2)
A (Baz 1) (Bar 2)
但是,如果我们尝试编写A (Baz 1) (Baz 2)
,则类型检查器会抱怨:
Couldn't match type 'BothBaz with 'NotBothBaz
arising from a use of A
In the expression: A (Baz 1) (Baz 2)
因此,类型检查器发现在这种情况下,toA
是参数BothBaz
,但是我们注释了typeA
以仅接受are NotBothBaz
,因此类型检查器抱怨BothBaz
与有所不同NotBothBaz
。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句