Haskell的类型系统是否与不一致的逻辑系统同构?如果是这样,会产生什么后果?

时间:2017-05-17 20:52:17

标签: haskell types logic undefined

在Haskell中有一个术语undefined :: a,据说是一个术语 表示糟糕的计算或无限循环。自undefined起 键入a,它们可以创建任何语法正确的类型 应用类型替换。所以undefined有任何类型,而o反义也是如此:任何类型都有undefined,这是任何类型(包括Void类型,右边?)的底部值。

Curry-Howard同构提供的不仅仅是命题作为类型,它还给出了习惯类型作为定理。

一个以所有命题为定理的逻辑系统被认为是不一致的。

那么,Haskell的类型系统是否与不一致的逻辑系统同构?

如果是这样,后果是什么?

如果Haskell的类型系统是一个不一致的证明系统,我们不能相信它吗?

可以用无undefined表示无限循环吗?

2 个答案:

答案 0 :(得分:16)

是的,逻辑不一致。

然而,这并不意味着类型系统是无用的。例如,类型系统确保我们永远不会尝试添加整数和字符串,或者从布尔值中调出第一个组件。这些通常是某些无类型语言中的运行时类型错误。

我们仍然会遇到一些运行时错误,例如非详尽的模式匹配(我个人禁止使用该语言:-P),errorundefined,异常等等。但至少我们没有类型相关的。我们也有非终止(通过无限递归),这是图灵完整语言中的必然之恶,并且无论如何都会导致不一致。

最后,我认为即使在这种不一致的环境中,库里 - 霍华德也很有用。例如。如果我想写f :: Either a b -> a,我可以立即发现这不是一个直觉主义定理,因此只能使用上面的运行时错误来实现f,所以开始时可能是一个坏主意,并且如果我需要这样的f,我应该重新考虑我的设计。

使用相同类型的Haskell定义良好的“完整”片段是否也有用,但没有上面提到的坏事?绝对!例如,它允许将()类型的任何表达式优化为值() - 毕竟它必须对其进行求值。此外,e :: a :~: b同样会被优化为类似unsafeCoerce Refl的东西,从而可以编写“类型之间相等的证明”,这只需要O(1)运行时成本。现在,不幸的是,这些证据必须在运行时进行评估,导致愚蠢的开销只是为了确保证明是“真实的”,而不是例如。伪装undefined

答案 1 :(得分:7)

  

如果Haskell的类型系统是一个不一致的证明系统,那么我们不能信任它吗?

正如@chi提到的,​​类型安全是一个比逻辑一致性弱得多的条件。引用Types and Programming Languages(如果您对此类事情感兴趣,请阅读好文章!),

  

安全=进步+保存

     

[...]   那么,我们想要知道的是,良好类型的术语不会被卡住。我们通过两个步骤展示这一点,通常称为进度保存定理。

     
      
  • 进度:一个包含良好类型的术语不会被卡住(无论是值还是根据评估规则都可以采取措施)。
  •   
  • 保存:如果一个类型很好的术语需要一个评估步骤,那么得到的术语也可以很好地输入。
  •   
     

这些属性一起告诉我们,在评估期间,良好类型的术语永远不会达到卡住状态。

请注意,这种类型安全的定义并不排除良好类型的术语永远循环或抛出异常的可能性。但是这两条规则确实可以保证,如果您已成功获得Int,那么确实是Int 而不是Bool或者SpoonEnnui。 (在Haskell中,这有点复杂,因为懒惰,但基本的想法仍然是正确的。)这是一个非常有用的属性,程序员学会在他们的日常工作流程中重要地依赖它!

请允许我说清楚。就像逻辑上不一致但仍然是类型安全的系统在实践中是有用且值得信赖的那样,类型不安全但仍然静态检查的系统在实践中也是有用的(尽管有些人可能不愿意称它们为#34;可信赖&#34)。查看JavaScript生态系统,其中"逐渐输入"诸如TypeScriptFlow之类的系统旨在为工作程序员提供额外的支持 - 因为VanillaJS甚至是中型代码库中的噩梦 - 没有像进步和保存这样做出大胆的承诺。 (我听说Python和PHP社区也逐渐(哈哈)采用类型。)TypeScript仍然成功地捕获了我在编程时产生的大部分人为错误,并且支持更改小型和大型代码,尽管不是& #34;类型安全"在正式意义上。

我想我的观点是,PL社区倾向于非常强调证明系统属性(例如类型安全),但这通常不是怎样的软件工程师完成他们的工作!我所知道的工程师关心的是一个工具是否可以帮助他们减少错误,而不是它是否被证明是安全的或一致的。我只是觉得每个小组都可以从另一个小组中学到很多东西!当超类型社区中聪明的人与无类型社区中的聪明人一起构建工程工具时,interesting things can happen

  

可以用无undefined表示无限循环吗?

不确定。很多方法。

loop = loop
fib = 1 : 1 : zipWith (+) fib (tail fib)
repeat x = unfoldr (const (Just x))
main = forever $ print "cheese"

从正式的角度来看,所有这些术语都是,但在实践中,你正在运行的事情非常重要。

如果您的问题确实是,那么编写循环术语的能力是否意味着不一致?那么简短的回答是肯定的,即let x = x in x :: forall a. a。这就是为什么像Agda这样的证明助手通常会使用终止检查程序来检查程序的语法并拒绝一般递归的可疑外观。更长的答案是no, not exactly - 您可以将程序的通用递归部分嵌入monad中,然后使用一些外部求值程序实现该偏好monad的语义。 (这正是Haskell纯粹采用的方法:将不纯的部分放在IO monad中,并将执行委托给运行时系统。)

总而言之,是的,Haskell作为一种逻辑是不一致的 - 每种类型都由具有各种运行时行为的无限个家族居住,因此您可以轻松地证明"任何命题。这(以及依赖类型的总体笨拙)是人们不使用Haskell作为定理证明者的原因。但是对于工程而言,对类型安全的较少保证通常是足够的,其中一些很有意思!