简体   繁体   English

Haskell:类型签名完全是什么意思?

[英]Haskell: What does it mean for a type signature to be total?

I have the following code, which outlines a language of boolean and arithmetic expressions: 我有以下代码,概述了布尔和算术表达式的语言:

data Exp a where
 Plus :: Exp Int -> Exp Int -> Exp Int
 Const :: (Show a) => a -> Exp a 
 Not :: Exp Bool -> Exp Bool
 And :: Exp Bool -> Exp Bool -> Exp Bool
 Greater :: Exp Int -> Exp Int -> Exp Bool

Below is code for a function that evaluates only arithmetic expressions: 以下是仅评估算术表达式的函数的代码:

evalA (Plus a b) = evalA a + evalA b
evalA (Const a) = a 

I am trying to figure out what type signature should be given to evalA such that it is total. 我试图弄清楚应该为evalA赋予哪种类型的签名,以使其完整。 However, I don't know what it means for for a type signature to be total. 但是,我不知道对一个类型签名进行总计意味着什么。 Any insights are appreciated. 任何见解都表示赞赏。

The other answer explains that "total" is a property of functions, not type signatures; 另一个答案说明“合计”是函数的属性,而不是类型签名。 then goes on to say that if you want your function to be total, you must cover the other constructors of the GADT. 然后继续说,如果您希望函数总计,则必须涵盖GADT的其他构造函数。 But this is not the whole story. 但这还不是全部。

The true story is that, for languages with advanced type systems like Haskell, "total" is a relation between functions and type signatures. 真正的故事是,对于具有高级类型系统(如Haskell)的语言,“总计”是函数和类型签名之间的关系 So it is true that it is not a property of type signatures (it doesn't make sense to say, "this type signature is total"); 因此,它确实不是类型签名的属性(说“这种类型签名是总计”是没有道理的); but it is also not the case that it is a property of functions (it doesn't make sense to say, in isolation, "this function is total"! 1 ). 但是它也不是函数的属性(单独说“这个函数是合计的!” 1 )没有意义。

So now, let's return to your question. 现在,让我们回到您的问题。 You say: 你说:

data Exp a where
 Plus :: Exp Int -> Exp Int -> Exp Int
 Const :: (Show a) => a -> Exp a 
 Not :: Exp Bool -> Exp Bool
 And :: Exp Bool -> Exp Bool -> Exp Bool
 Greater :: Exp Int -> Exp Int -> Exp Bool

evalA (Plus a b) = evalA a + evalA b
evalA (Const a) = a 

Given our updated understanding, we can now ask a new and better, more precise question, which is: does there exist a type signature for evalA that, when paired with this implementation, causes the pairing to be total? 有了我们最新的理解,我们现在可以提出一个新的更好,更精确的问题,即:是否存在evalA的类型签名,当与该实现配对时,该配对导致总的配对? The answer to this better question is yes , contrary to the claim in the other answer that says you must implement more cases of evalA . 这个更好的问题的答案是肯定的 ,与另一个答案中的说法相反,该说法指出您必须实施更多的evalA In particular, if we write 特别是如果我们写

evalA :: Exp Int -> Int
evalA (Plus a b) = evalA a + evalA b
evalA (Const a) = a

then any well-typed application of evalA to a finite input will, in finite time, produce a non-bottom answer. 则在有限时间内将任何类型正确的evalA应用于有限输入都将产生一个非底答案。 (This is one sensible meaning of "total" for functions.) (这是功能“合计”的一种明智含义。)

Why may we ignore the Not , And , and Greater cases? 为什么我们可以忽略NotAndGreater情况? Why, because we have demanded that the input have type Exp Int , and any well-typed term whose outer constructor is Not , And , or Greater will actually have type Exp Bool -- and so the application would not be well typed. 为什么,因为我们要求输入的类型为Exp Int ,并且外部构造函数为NotAndGreater任何类型良好的术语实际上都将具有Exp Bool类型-因此应用程序的类型将不正确。 So this can't crash with an inexhaustive pattern match error, as one might worry! 因此,这不会因模式匹配错误而导致崩溃,您可能会担心!

1 One could say "this function, given any type signature which type-checks, is total". 1 可能会说“给定类型检查的任何类型签名,此功能都是合计的”。 Indeed, it is common to say "this function is total" as a convenient shorthand to mean that. 实际上,通常说“此功能是全部功能”作为表示该功能的方便快捷方式。 The other answer shows how to make your function total no matter what (correct) type signature is given. 另一个答案显示了无论给定哪种(正确)类型签名,如何使您的函数合计。

A type signature can not be "total" or "non-total". 类型签名不能为“总计”或“非总计”。 At best, with such terminology someone could refer to types claiming that a result is always returned (except for non termination): 充其量,使用这样的术语,有人可以引用声称总是返回结果的类型(非终止除外):

foo :: .. -> .. -> Result

in contrast to types wrapping the result in Maybe or something similar to denote that the result might not be there, after all: 毕竟,与将结果包装在Maybe或类似的类型相反,表明结果可能不存在:

foo :: .. -> .. -> Maybe Result

This would be a stretch of terminology, and I would not use it in that way. 这只是术语的延伸,我不会那样使用。

Anyway, the Exp a type you mention is a GADT, which is a rather advanced feature of Haskell. 无论如何,您提到的Exp a类型是GADT,这是Haskell的一项高级功能。 It allows you to define 它允许您定义

evalA :: Exp a -> a
evalA (Plus a b) = evalA a + evalA b
evalA (Const a) = a 
-- you should cover the other cases as well here

without requiring you to wrap the return type using Maybe or something similar, as it would happen with regular algebraic types. 而不要求您使用Maybe或类似方法包装返回类型,这与常规代数类型一样。


Let's consider a simpler example: a language with integer and boolean literals, only. 让我们考虑一个更简单的示例:仅包含整数和布尔文字的语言。

data Exp where
  I :: Int -> Exp
  B :: Bool -> Exp

Now, it is impossible to define, say, semExpInt :: Exp -> Int without using some ugly trick: 现在,不使用一些难看的技巧就无法定义semExpInt :: Exp -> Int

semExpInt :: Exp -> Int
semExpInt (I i) = i                      -- OK!
semExpInt (B b) = error "not an Int!"    -- ugly!

In the latter case, we need to raise a runtime error, fail to terminate, or return an arbitrary integer. 在后一种情况下,我们需要引发运行时错误,无法终止或返回任意整数。 Essentially, we find a "runtime type error" inside Exp , which represents a value of the wrong type ( Bool instead of Int ). 本质上,我们在Exp内发现一个“运行时类型错误”,它表示错误类型的值( Bool而不是Int )。

If we try semExpBool :: Exp -> Bool we have a similar problem. 如果我们尝试semExpBool :: Exp -> Bool遇到类似的问题。

We could, and should, report the error using Maybe : 我们可以并且应该使用Maybe报告错误:

semExpInt :: Exp -> Maybe Int
semExpInt (I i) = Just i   -- OK
semExpInt (B b) = Nothing  -- OK, no result here

This is fine, but inconvenient. 很好,但是不方便。 We are still reporting "runtime errors in the expression" in some way ( Nothing ). 我们仍在以某种方式报告“表达式中的运行时错误”( Nothing )。 It would be better if we could avoid that, by taking as input an expression which we know would be of the right type. 如果我们可以避免这种情况,那就最好通过输入一个我们知道正确类型的表达式作为输入。 With GADTs we can write 使用GADT,我们可以编写

data Exp t where
  I :: Int -> Exp Int
  B :: Bool -> Exp Bool

semExpInt :: Exp Int -> Int
semExpInt (I i) = i     -- no other cases to handle!

semExpBool :: Exp Bool -> Bool
semExpBool (B b) = b     -- no other cases to handle!

Or, even better, we can joint the two functions in a single one: 或者,甚至更好的是,我们可以将两个功能合而为一:

semExp :: Exp t -> t
semExp (I i) = i
semExp (B b) = b

Here, we claim that the result type is precisely the type t which is carried by the input type Exp t . 在这里,我们宣称的结果类型正是类型t其由输入类型进行Exp t So, this function will return an Int , or a Bool , depending on the input type. 因此,此函数将根据输入类型返回IntBool

this is even more convenient when adding operators to the expression. 将运算符添加到表达式时,这甚至更加方便。 For instance, 例如,

data Exp where
  I :: Int -> Exp
  B :: Bool -> Exp
  And :: Exp -> Exp -> Exp

allows And (B True) (B False) , which is nice, but also allows And (I 2) (B False) which is nonsensical, since And should be used on booleans, only. 允许And (B True) (B False) ,这很好,但也允许And (I 2) (B False)毫无意义,因为And只应用于布尔值。 This would have to be handled in the semantics: 这必须用语义来处理:

semExpBool :: Exp -> Maybe Bool
semExpBool (I i) = Nothing
semExpBool (B b) = Just b
semExpBool (And e1 e2) = case (semExpBool e1, semExpBool e3) of
   (Just b1, Just b2) -> Just (b1 && b2)
   _                  -> Nothing          -- some arg was not a bool!

With GADTs, instead, we can express this: 使用GADT,我们可以这样表达:

data Exp t where
  I :: Int -> Exp Int
  B :: Bool -> Exp Bool
  And :: Exp Bool -> Exp Bool -> Exp Bool

Now, And (I 2) (B False) is disallowed since And requires a Exp Bool argument, and I 2 is not such. 现在, And (I 2) (B False)被禁止,因为And需要Exp Bool参数,而I 2不是这样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM