无类别术语的解释

时间:2012-04-28 02:45:57

标签: haskell gadt

我从大学继续我的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

2 个答案:

答案 0 :(得分:4)

我认为你和你的GADT走错了路。要了解原因,我们首先要查看Expr及其评估者(问题的第一部分)的无类型版本。

以下是一些可以构造的Expr类型的值:

expr1 = Con (IntValue 42)
expr2 = Con (BoolValue True)
expr3 = Con (BoolValue False)

到目前为止,非常好:expr1表示整数常量42,expr2expr3布尔常量TrueFalse。将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))

expr4expr5没问题;它们分别代表表达式True && TrueTrue && (False && False)。我们希望他们可以评估为TrueFalse,但很快就会对此进行评估。但是,expr6看起来很可疑:它代表了True && 42这个没有意义的表达式(至少在Haskell中!)。

到目前为止我们看到的表达式(数字6除外)都有一个值:expr1的整数值为42,其余的是布尔值(TrueFalse,{{ 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进来的地方!我们不打算为评估的每个可能的结果类型(上面的CharDouble创建单独的表达式),而是标记"标记"表达式类型本身具有它将评估的类型。因此,我们肯定评估类型IntExpr的值的结果将是BoolExpr,而Expr Int将为我们提供Int }。实际上,我们让编译器为我们进行类型检查,而不是在运行时(如上面的函数Expr Bool中那样)。

目前,我们只有两种构造表达式的方法:制作一个常量表达式,以及将两个布尔值放在一起的'和' - 两个布尔值。第一种方式是这样的:如果我们有Bool,我们将其变为logicalAndValueInt变为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)) ,但expr6And (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))ExprInt或其他什么)。

答案 1 :(得分:2)

使用GADT的目的是确保所有表达式都按照构造进行良好类型。这意味着,如果您有一个Expr Int,就会知道它的类型很好并且评估为Int

要强制执行此操作,您需要确保使用其包含的类型标记常量表达式,以便Con 0具有Expr Int类型而Con TrueExpr Bool。< / p>

Con :: v -> Expr v

同样,您需要确保And只能用于评估为Bool的表达式。

And :: Expr Bool -> Expr Bool -> Expr Bool

这样,Con 0 `And` Con 1之类的东西甚至都不会编译,因为常量的类型为Expr IntAnd需要Expr Bool

一旦你正确设置了这一点,实施est :: Expr e -> e应该是一项微不足道的工作。