类型系统常见问题

该文章的作者(我们今天将其翻译发表)说, 文章及其评论是其写作的灵感来源。 据他介绍,IT专家对类型有误解,使用不正确的术语,并且在讨论与类型有关的问题时得出错误的结论。 他指出,他不是静态类型系统的捍卫者。 唯一困扰他的是正确使用术语。 这允许进行建设性的讨论。 作者说,他是自发编写的,但希望其中没有错误。 如果他感到困惑,他要求让知道。



我们一劳永逸地了解在讨论类型系统时会引起混乱的原因。

动态打字和缺乏打字


有人认为动态类型系统与未类型类型系统相同。 缺少类型化意味着在特定类型系统中,区分类型毫无意义。 即使类型系统中仅存在一个类型,也无法区分类型。 例如:

  • 在汇编器中,唯一的类型是一串位。
  • 在lambda演算中,唯一的类型是函数。

有人可能会这样说:“但是,它有什么区别-动态键入或缺少键入-这对我也是一个问题。” 但这实际上是一个重要的大问题。 事实是,如果将动态类型等同于缺少类型,则意味着自动采用动态类型系统与静态类型系统相反的事实。 结果,形成了两个相反的程序员阵营-动态类型的阵营和静态类型的阵营(这在我们相应的部分将看到,这是错误的)。

不限制变量值范围的语言称为无类型语言:它们没有类型,或者,同样,它们只有一种包含所有值的通用类型。
类型系统,卢卡·卡德利(Luca Cardelli)

编程语言具有一项有趣的功能,可让您将其世界大致分为两类:

  • 无类型语言-程序只需执行即可。 这很快发生,而无需尝试检查“表单的一致性”。
  • 键入语言-尝试在编译期间或程序执行期间检查“格式的一致性”。

本杰明·皮尔斯(Benjamin Pearce),“用于编程语言的类型系统”

动态和静态输入


动态类型系统是其中(在程序执行期间)动态检查类型的系统。 静态类型系统是其中(在编译或代码转换期间)静态检查类型的系统。

这些系统中的一个与另一个相反吗? 不,不是。 两种类型的系统都可以使用相同的语言。 实际上,大多数静态类型系统也具有动态类型检查。 例如,考虑输入输出操作(输入输出,IO)的验证。 假设您需要读取必须输入数字的用户提供的数据。 您将在程序执行期间检查该数字是否是解析相应行的结果(作为解析结果,可能会引发异常或将返回类似NaN内容)。 当您检查用户输入的数据时,要弄清它们是否可以视为数字-您将执行动态类型检查。

结果,我们可以注意到静态和动态类型之间没有对抗。 您可以使用相同的语言,也可以同时使用其他语言。

此外,应该注意的是,静态类型检查是一个复杂的过程。 有时很难静态地检查程序的某些部分。 因此,您可以采用动态检查来代替应用静态类型检查。

建议将静态类型系统视为静态检查的类型。 动态类型系统类似于动态检查的类型。

使用静态类型是否意味着在编译时就知道类型?


本部分标题中提出的问题可以否定回答。 如果打开任何解析器(包括JavaScript解析器)的源代码,则可以看到解析器在解析过程中知道值的类型(这是编译过程的一部分)。

 let x = "test"; 

事实证明,解析器知道"test"是一个字符串。 这会使JavaScript成为静态类型的语言吗? 不,不是。

逐步打字


渐变类型系统是静态类型系统,它使您可以跳过程序某些部分的类型检查。 例如,在TypeScript中,这是使用any@ts-ignore

一方面,这使类型系统的安全性降低。 另一方面,具有渐进式类型的类型系统使您可以将类型描述逐步添加到具有动态类型的语言中。

可靠和不可靠的类型系统


如果您使用可靠的类型系统(声音类型系统),则在该类型检查过程中如果该程序中与类型有关的错误不会被“批准”。 使用不健全的类型系统会导致程序中出现类型错误的可能性。 没错,找到答案后不要惊慌。 实际上,这可能不会影响您。 可靠性或可靠性是类型检验算法的数学特性,需要证明。 许多现有的编译器(内部是类型检查系统)都不可靠。

如果要使用可靠的类型系统,请看一下使用Hindley-Milner类型系统的ML系列的编程语言。

此外,必须理解,可靠的类型系统不会跳过错误的程序(不会给出错误的肯定测试结果,考虑到错误的程序是正确的),但是可能不会错过正确的程序(它可能给出错误的否定测试结果)。

永不拒绝正确程序的类型系统称为完成。

类型系统是否既可靠又完整? 据我所知,这种类型的系统不存在。 我不确定到底如何,但是在我看来,如果基于戈德尔的不完备性定理,这类类型系统的存在从根本上是不可能的(尽管我可能是错的)。

弱而强的打字


我发现使用术语“弱打字”和“强打字”是不合适的。 这些术语是模棱两可的,使用起来可能会带来混淆,而不是清楚。 让我给你一些报价。

这些语言可以说是具有弱类型检查的语言(或弱类型语言,因为它们通常在各种出版物中被称为)。 在该语言中使用弱类型检查意味着会静态检测到一些不安全的操作,而某些则不会。 此类语言中类型检查的“弱点”相差很大。
类型系统,卢卡·卡德利(Luca Cardelli)

对类型系统进行分类的最常见方法可能是将它们分为具有“弱”和“强”类型的系统。 只能后悔,因为这些词本身实际上没有任何意义。 在有限的程度上可以比较两种具有非常相似的类型系统的语言,并选择其中一种具有比第二种语言更强大的类型系统。 在其他情况下,术语“强类型”和“弱类型”完全没有意义。
“在讨论类型系统之前要知道的事”,史蒂夫·克拉伯尼克

术语“强类型”和“弱类型”非常模棱两可。 以下是其用法的一些示例:

  • 有时,“强类型”被理解为“静态类型”。 进行这样的“替换”并不难,但是就静态类型而言,最好将其简单地称为“静态”。 事实是,大多数程序员清楚地理解了这个术语。
  • 有时,当他们说“强类型”时,它们的意思是“没有隐式类型转换”。 例如,在JavaScript中,可以使用诸如"a" - 1类的表达式。 这可以称为“弱类型”模型。 但是几乎所有的语言都为程序员提供了一些隐式类型转换的机会,例如,支持将整数自动转换为1-1.1这样的表达式中的浮点数。 实际上,大多数以这种方式使用“强类型”一词的专业人员会区分“可接受的”和“不可接受的”类型转换。 但是这种类型转换之间没有公认的界限。 转换的“可接受性”和“不可接受性”是一种主观评估,取决于特定人员的意见。
  • 有时,具有“强类型”的语言称为无法绕过它们中存在的类型系统规则的语言。
  • 有时,“强类型”是指拥有一个可以安全使用内存的类型系统。 C语言是内存不安全的一种著名示例。 例如,如果xs是由四个数字组成的数组,则C会批准使用xs[5]xs[1000]类的结构的代码,而不会出现任何问题。 它们将允许您访问位于分配用于存储xs数组内容的地址之后的内存。

类型,加里·伯纳德

静态类型语言是否需要类型声明?


静态类型语言并不总是需要类型声明。 有时类型系统可以推断类型(通过基于代码结构进行假设)。 这是一个示例(TypeScript):

 const x = "test"; 

类型系统知道"test"是一个字符串(该知识基于代码解析的规则)。 类型系统还知道x是一个常数,即x的值不能重新分配。 结果,可以得出结论, x是字符串类型。
这是另一个示例(流程):

 const add = (x, y) => x / y //            ^        [1]   . add(1, "2") 

类型检查系统看到我们调用了add函数,并为其传递了一个数字和一个字符串。 这将分析函数声明。 类型检查系统知道,为了执行除法运算,数字必须在相应运算符的左侧和右侧。 除法运算中涉及的操作数之一不是数字。 结果,我们被告知该错误。

此处没有类型声明,但这不会阻止上述程序进行静态类型检查。 如果您在现实世界中遇到类似情况,则迟早必须声明一些类型。 类型系统不能推断出绝对的所有类型。 但是,您需要了解一种语言可以称为“静态”语言,不是因为它使用了类型声明,而是因为在程序启动之前会检查类型。

TypeScript是不安全的语言,因为用它编写的代码已编译为JavaScript代码?


TypeScript是一种不健全的语言。 因此,上面写的代码可能会变成不安全的应用程序。 但这与它编译成什么无关。

大多数桌面编译器将程序转换为类似于汇编语言的程序。 汇编器是一种比JS不安全的语言。

在这里,如果您回到由于JS编译导致TS不安全的想法,您可能会想到以下几点:“编译的代码是在浏览器中执行的,JS是一种不安全的语言,它可以用null代替预期的行”。 思想是明智的。 但是,这再次没有给出将TS称为不安全语言的理由。 为了使TS能够保证应用程序内的安全性,您需要将“防御机制”放置在TS代码与外界进行交互的那些地方。 也就是说,例如,您需要检查通过输入输出机制输入程序的数据的正确性。 假设这可以是检查用户输入的内容,检查服务器的响应,检查从浏览器存储中读取的数据等等。

例如,榆树中这种“防御机制”的作用是由“ 港口 ”扮演的。 在TS中,您可以使用io-ts之类的方法。

相应的“保护机制”在静态和动态类型系统之间建立了桥梁。

这是一个简化的示例:

 const makeSureIsNumber = (x: any) => {  const result = parseFloat(x);  if (isNaN(result)) {    throw Error("Not a number");  }  return result; } const read = (input: any) => {  try {    const n = makeSureIsNumber(input);    //     n, ,      //              // makeSureIsNumber "" , n    } catch (e) { } } 

是否只有编译器才需要类型?


类型只是向编译器提供提示所需的技巧。
沃特·梅滕斯

类型仅是编译器所需要的吗? 这是一个哲学问题。 人而不是汽车需要类型。 编译器需要类型,因为它们是人编写的程序。

类型的现象是由于人而存在的。 在一个人以“数据类型”的形式感知事物之前,类型是不存在的。 人类的思想将不同的实体分为不同的类别。 没有观察者,类型就没有意义。

让我们安排一个思想实验。 想想生活游戏。 您有一个由正方形单元格组成的二维网格。 每个单元可以处于两种可能的状态。 它可以是“活着”或“死亡”。 每个小区可以与其八个邻居进行交互。 这些是垂直,水平或对角边界的单元格。 在查找系统的下一个状态的过程中,适用以下规则:

  • 人口密度低的情况下,少于两个“活着”邻居的“活着”牢房“死亡”。
  • 一个有两个或三个“活着”邻居的“活着”牢房可以生存并进入下一代。
  • 一个“活着”的细胞,与三个以上“活着”的邻居,“死亡”,就像人口过剩一样。
  • 正好有三个“活着的”邻居的“死”细胞变成“活的”,就像种群繁殖一样。

从外观上看,它看起来像一个领域,分为方形单元格,它们不断地“打开”和“关闭”。 在这里您可以看一下。

如果您观察生命一段时间,那么像“滑翔机”这样的稳定​​结构可能会出现在场上。


滑翔机

见他吗 滑翔机在屏幕上移动。 对不对 现在让我们放慢一点。 这个滑翔机真的存在吗? 这些只是出现和消失的单个方块。 但是我们的大脑可以感觉到这种结构是客观存在的。

另外,我们可以说“滑翔机”之所以存在是因为正方形不是独立的(它们依赖于邻居),即使“滑翔机”本身不存在,形式上也存在“滑翔机”柏拉图式的想法。

总结


考虑使用类型化编程语言编写的任何程序。 我们可以观察类型。 对不对 但是程序会编译为机器代码。 在这些代码中,与原始程序中的内容相同(尽管人们很难读取程序的机器表示)。 从计算机的角度来看,没有类型。 他只看到比特序列-零和一的集合(“死”和“活”单元)。 类型适用于人,而不适用于汽车。

亲爱的读者们! 您认为哪种类型的系统适合进行Web开发?

Source: https://habr.com/ru/post/zh-CN462481/


All Articles