eag*_*ute 4 haskell types functional-programming
我是Haskell的新手.对不起,如果这个问题有明显的答案.
我有
data Tmp = Foo Int
| Bar Int
| Baz Int
Run Code Online (Sandbox Code Playgroud)
和
data Test = A Tmp Tmp
Run Code Online (Sandbox Code Playgroud)
构造函数A Tmp Tmp可以使用任何构造函数,Tmp除了
A (Baz i) (Baz j)where i和j是任意Ints.有没有什么办法可以限制第二Tmp中A Tmp Tmp被Baz当第一Tmp已经是Baz?
Tox*_*ris 13
答案取决于您希望如何强制执行限制:在运行时或编译时.
要在运行时强制执行限制,可以添加一个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
Run Code Online (Sandbox Code Playgroud)
这种技术的好处是您根本不必更改数据类型.缺点是限制仅在运行时强制执行.
要在编译时强制执行限制,您需要以某种方式更改数据类型.当前数据类型的问题在于类型检查器无法区分由Foo和Bar构造的值构造的值Baz.对于类型检查器,这些都是Tmp值,因此类型检查器不能强制某些Tmp值正常而其他值不正确.因此,我们必须更改数据类型以编码类型中Tmp值的"Bazness" .
对该类型中的Bazness进行编码的一个选项是重构Tmp如下:
data TmpNotBaz
= Foo Int
| Bar Int
data Tmp
= NotBaz TmpNotBaz
| Baz Int
Run Code Online (Sandbox Code Playgroud)
现在很清楚,类型的值TmpNotBaz不能Baz,但类型的值Tmp可以Baz.这个想法的好处是它只使用基本的Haskell功能.一个小缺点是您需要将调用NotBaz放入代码中.主要缺点是,我们仍然不能直接表达"的其中一个参数的想法A可能是Baz,如果对方不是".我们必须编写多个版本A:
data Test
= A1 TmpNotBaz Tmp
| A2 Tmp TmpNotBaz
Run Code Online (Sandbox Code Playgroud)
现在我们可以通过选择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
Run Code Online (Sandbox Code Playgroud)
注意构造函数的类型签名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)
Run Code Online (Sandbox Code Playgroud)
所以类型检查器发现在这种情况下,参数A是BothBaz,但我们注释的类型A只接受参数NotBothBaz,因此类型检查器抱怨BothBaz不同NotBothBaz.