我从大学继续我的Haskell主题练习,我得到了以下内容:
data Expr = Con Value
| And Expr Expr
data Value = IntValue Int
| BoolValue Bool
est :: Expr -> Val
est (Con v) = v
est (And x y) =
case (est x, est y) of
(BoolValue bool1, BoolValue bool2) -> BoolValue $ bool1 && bool2
_ -> error "And: expected Boolean arguments"
我不确定它的真正含义。它似乎是Expr
中定义的术语的评估者。有人可以向我解释一下吗?我的练习涉及到我将其转换成GADT,我已经完成了:
data Expr e where
Con :: Val -> Expr Val
And :: Expr e -> Expr e -> Expr e
现在他们要求我静态地实现以下内容并使其安全:
est :: Expr e -> e
est _ = -- implement this
答案 0 :(得分:4)
我认为你和你的GADT走错了路。要了解原因,我们首先要查看Expr
及其评估者(问题的第一部分)的无类型版本。
以下是一些可以构造的Expr
类型的值:
expr1 = Con (IntValue 42)
expr2 = Con (BoolValue True)
expr3 = Con (BoolValue False)
到目前为止,非常好:expr1
表示整数常量42,expr2
和expr3
布尔常量True
和False
。将Expr
作为最外层构造函数的Con
类型的所有值基本上都是这样的。
当我们将And
构造函数添加到混合中时,事情开始变得有趣:
expr4 = And (Con (BoolValue True)) (Con (BoolValue True))
expr5 = And (Con (BoolValue True)) (And (Con (BoolValue False)) (Con (BoolValue False)))
expr6 = And (Con (BoolValue True)) (Con (IntValue 42))
expr4
和expr5
没问题;它们分别代表表达式True && True
和True && (False && False)
。我们希望他们可以评估为True
和False
,但很快就会对此进行评估。但是,expr6
看起来很可疑:它代表了True && 42
这个没有意义的表达式(至少在Haskell中!)。
到目前为止我们看到的表达式(数字6除外)都有一个值:expr1
的整数值为42,其余的是布尔值(True
,False
,{{ 1}},True
代表False
s 2到5)。如您所见,这些值可以是整数或布尔值,因此可以表示为expr
类型的值。
我们可以编写一个评估者,该评估者使用Value
并返回其Expr
。换句话说,评估者应该返回一个常量表达式的值,如果表达式是逻辑'和',它应该返回逻辑'和'组成表达式的值,需要是布尔值 - 你不能取整数的逻辑Value
和布尔值,或两个整数。在代码中,这转换为
and
这只是第一个est :: Expr -> Value -- takes an Expr and returns its Value
est (Con value) = value -- the value of a constant expression is the wrapped value
est (And e1 e2) = let value1 = est e1 -- the value of the first operand
value2 = est e2 -- the value of the second operand
in logicalAndValue value1 value2
logicalAndValue :: Value -> Value -> Value
logicalAndValue (BoolValue b1) (BoolValue b2) = BoolValue (b1 && b2)
logicalAndValue (BoolValue b1) (IntValue i2) = error "Can't take the logical 'and' of a boolean and an integer"
logicalAndValue (IntValue i1) (BoolValue b2) = error "Can't take the logical 'and' of an integer and a boolean"
logicalAndValue (IntValue i1) (IntValue i2) = error "Can't take the logical 'and' of two integers"
的更详细的版本,其中包括采用逻辑'和'将两个已计算的表达式分解为一个单独的函数和稍微提供更多信息的错误消息。
好的,希望这能回答你的第一个问题!问题归结为est
值可以具有布尔值或整数值,并且您不能看到""那种类型,所以可以将Expr
两个表达式组合在一起,这是没有意义的。
解决此问题的一种方法是将And
拆分为两种新的表达式类型,一种具有整数值,另一种具有布尔值。这看起来像(我将给出上面使用的Expr
的等价物):
expr
有两件事值得注意:我们已经摆脱了data IntExpr = ConInt Int
expr1 :: IntExpr
expr1 = ConInt 42
data BoolExpr = ConBool Bool
| AndBool BoolExpr BoolExpr
expr2 :: BoolExpr
expr2 = ConBool True
expr3 = ConBool False
expr4 = AndBool (ConBool True) (ConBool True)
expr5 = AndBool (ConBool True) (AndBool (ConBool False) (ConBool False))
类型,现在它已经不可能构建等效的{{1}这是因为它的显式翻译Value
将被编译器拒绝(可能值得尝试这一点),因为类型错误:expr6
类型为AndBool (ConBool True) (ConInt 42)
并且你不能将它用作ConInt 42
的第二个参数。
评估者还需要两个版本,一个用于整数表达式,另一个用于布尔表达式;让我们来写吧! IntExpr
应该有AndBool
个值,而IntExpr
应该评估为Int
s:
BoolExpr
您可以想象,当您添加更多类型的表达式(如Bool
,列表, evalInt :: IntExpr -> Int
evalInt (ConInt n) = n
evalBool :: BoolExpr -> Bool
evalBool (ConBool b) = b
evalBool (AndBool e1 e2) = let v1 = evalBool e1 -- v1 is a Bool
v2 = evalBool e2 -- v2 as well
in v1 && v2
)或组合表达式(如添加两个数字,构建)时,这会变得非常烦人'如果'表达式甚至变量,而事先没有给出类型...
这是GADT进来的地方!我们不打算为评估的每个可能的结果类型(上面的Char
和Double
创建单独的表达式),而是标记"标记"表达式类型本身具有它将评估的类型。因此,我们肯定评估类型IntExpr
的值的结果将是BoolExpr
,而Expr Int
将为我们提供Int
}。实际上,我们让编译器为我们进行类型检查,而不是在运行时(如上面的函数Expr Bool
中那样)。
目前,我们只有两种构造表达式的方法:制作一个常量表达式,以及将两个布尔值放在一起的'和' - 两个布尔值。第一种方式是这样的:如果我们有Bool
,我们将其变为logicalAndValue
,Int
变为Expr Int
,依此类推。因此,"的类型签名使得常量表达"构造函数将是:
Bool
第二个构造函数接受两个表示布尔值的表达式(因此这两个表达式的类型为Expr Bool
)并返回另一个带有布尔值的表达式,即此构造函数的类型为
Con :: v -> Expr v
将各个部分放在一起,我们得到以下Expr Bool
类型:
And :: Expr Bool -> Expr Bool -> Expr Bool
一些示例值:
Expr
再次,相当于data Expr e where
Con :: v -> Expr v
And :: Expr Bool -> Expr Bool -> Expr Bool
没有通过类型检查器:它将是expr1 :: Expr Int
expr1 = Con 42
expr2 :: Expr Bool
expr2 = Con True
expr3 :: Expr Bool
expr3 = Con False
expr4 :: Expr Bool
expr4 = And (Con True) (Con True)
expr5 :: Expr
expr5 = And (Con True) (And (Con False) (Con False))
,但expr6
是And (Con True) (Con 42)
因此可以&#39 ; t被用作Con 42
的参数 - 它必须是Expr Int
。
评估员现在变得非常。给定And
(记住,这意味着它是一个类型为Expr Bool
的表达式),它返回Expr e
。常量表达式的值是常量本身,以及逻辑'和'的值。表达是'和'操作数的值,我们确定这些值是e
s。这给出了:
e
你给出的GADT直接相当于无类型的Bool
,它仍然允许你构建“坏”' est :: Expr e -> e
est (Con v) = v
est (And e1 e2) = let b1 = est e1 -- this will be a Bool, since e1 is an Expr Bool
b2 = est e2 -- likewise
in b1 && b2
等值。
摆脱'价值'类型,我们可以更精确地说明表达式的类型;而不是说"表达式的类型是整数或布尔值,但我还不知道"在评估表达式时遇到问题,我们从一开始就确保我们知道表达式的值的类型,并且我们不会以不合理的方式将它们组合在一起。
我希望你到目前为止 - 所有这些类型,价值观和表达方式都会让人感到困惑! - 并且您可以通过自己扩展Expr
类型和评估器来进行一些实验。
要尝试的简单方法是创建一个表达式,使用字符串或字符常量添加两个整数值,或者制作一个&if-then-then-else'表达式,它带有三个参数:布尔类型的第一个,同一类型的第二个和第三个(但该类型可以是And (Con (BoolValue True)) (Con (IntValue 42))
,Expr
,Int
或其他什么)。
答案 1 :(得分:2)
使用GADT的目的是确保所有表达式都按照构造进行良好类型。这意味着,如果您有一个Expr Int
,就会知道它的类型很好并且评估为Int
。
要强制执行此操作,您需要确保使用其包含的类型标记常量表达式,以便Con 0
具有Expr Int
类型而Con True
为Expr Bool
。< / p>
Con :: v -> Expr v
同样,您需要确保And
只能用于评估为Bool
的表达式。
And :: Expr Bool -> Expr Bool -> Expr Bool
这样,Con 0 `And` Con 1
之类的东西甚至都不会编译,因为常量的类型为Expr Int
而And
需要Expr Bool
。
一旦你正确设置了这一点,实施est :: Expr e -> e
应该是一项微不足道的工作。