【问题标题】:Why does OCaml have mandatory distinct float and int literal syntax?为什么 OCaml 具有强制不同的 float 和 int 字面量语法?
【发布时间】:2020-10-07 12:48:28
【问题描述】:

OCaml 对 BOTH 都有不同的语法:

  • 浮点运算与整数运算。浮点运算以点结尾:+.
  • float 文字与 int 文字。浮点文字以点结尾:3.
# 3 + 3;;
- : int = 6
# 3. +. 3.;;
- : float = 6.
# 3. + 3;;
Error: This expression has type float but an expression was expected of type
         int

我可以看到使用其中一种机制来消除歧义,但为什么需要两个总是? 例如,我可以看到 .sometimes 在文字末尾需要的情况,但不是为什么 3 +. 3 需要它,因为 OCaml 可以找出我们需要浮点运算,因为我们说我们想要浮点运算。

我正在寻找基于与其他语言功能交互的具体技术理由,而不是来自人体工程学的意见或论据。

【问题讨论】:

    标签: compiler-errors ocaml static-typing


    【解决方案1】:

    简而言之,这是因为(+.)(+) 以及11. 表示不同的值,尽管它们看起来很相似。这些值具有不同的类型、不同的表示形式和不同的语义。使(+)1 在不同的上下文中工作相同的语言特性称为ad-hoc 多态性,它在OCaml 中不存在。 OCaml 不会尝试从程序的文本表示形式及其类型中推断您将使用哪个值,而是根据您正在使用的值以及您的方式推断程序的类型是什么使用它们。这是 ML 与其他一些相反的语言的一个重要区别,即它们具有用户指定的类型,然后推断与该类型匹配的正确值并检查这些值是否被正确使用。在 ML 中,输入是程序,输出是程序的类型,它 (a) 用于证明程序是良好类型的并且在运行时不会有任何类型错误,并且 (b) 用于生成删除所有推断类型的本机和高性能代码。同样重要的是要理解 OCaml 中的类型推断不是一个方便的实用程序,它可以让您在可以推断出类型时省略它们(如 C++ 的 auto 或 Scala 中的本地类型推断)。相反,它是编译过程和语言语义中的主要步骤,因为 OCaml 必须能够推断任何程序的类型。我们偶尔在 OCaml 程序中编写的类型仅用作约束,从不作为输入。此外,类型注释永远不会改变程序的行为。好吧,除非 GADT 发挥作用,但这是一个完全不同的故事。

    为了更深入地了解,我们应该记得 OCaml 下的类型推断算法是语法驱动声明性。语法驱动意味着程序语法完全定义了该程序的类型。此外,该算法确保如果类型存在(即程序类型良好),那么这个推断的类型是唯一的和主要的。即,要么没有其他类型代表这个程序,要么所有其他类型都是推断类型的实例。声明性意味着如何将类型分配给程序的规则用declarative type rules 描述。该算法将程序正式转换为类型,从而启用/确保Curry-Howard 对应,计算机程序和数学证明之间的深度连接。这使我们能够谈论程序的正确性,反之亦然,谈论真实的正确性,即用程序证明我们的定理是正确的。这让我们回到了 OCaml/ML 的历史,它最初是用于 LCF(可计算函数逻辑)定理证明器的元语言 (ML)。

    鉴于我们同意我们不想失去该语言的重要属性,问题仍然悬而未决,为什么我们不能实现语法驱动和可声明的临时多态性。好吧,事实上,我们可以,例如,Haskell 有一个。还有一些关于将modular implicits 添加到 OCaml 的工作。但这一切都伴随着权衡,最终将是一种完全不同的语言。到目前为止,在 OCaml 中,每个值都有一个类型或类型方案,并且在 OCaml 运行时系统中没有类型可以表示适用于整数和浮点数的 + 运算符。

    话虽如此,OCaml 不诚实地说您可以定义自己的 (+) 运算符,其类型为 number -> number -> number,其中 number 是存在的 type number = Num : 'a typeid * 'a -> number,带有单独的表每个typeid 的操作都存储在一些隐藏的哈希表中。但这将是动态类型(查看typeid 是如何打包在每个值中的),它与静态类型完全不同,它可以在程序运行之前为您提供有关程序的保证(在我们的示例中,这个(+) 函数可能会失败在运行时,我们的输入规则不是声明性的,而是(+) 运算符实现所固有的)。

    【讨论】:

      【解决方案2】:

      每个函数都有一个类型,这就是 OCaml 语法的原因。如果你传递了一个不合适的参数,编译器会拒绝你的代码并报错。

      这似乎很明显,但在任何函数中也是如此,包括基本的数学运算符:名为 +. 的函数具有以下类型:

      val (+.): float -> float -> float
      

      就是这样。如果你给出一个 int 而不是 float,编译器会告诉你你的代码是错误的。 OCaml 没有计算任何东西。编译器只检查参数是否与函数签名有效。他们不是?错误。

      您可能会争辩说这一点是错误的,因为任何 C 编译器都会接受您的代码行并按预期生成一个浮点数。但这是另一种语言,即使 可以¹ 工作,这在 OCaml 中也是不允许的。你无法逃避类型。

      language specification 很清楚:每个函数都是由参数的类型定义的,不能给出多类型参数。

      这允许编译器轻松优化代码,但您必须将参数显式转换为函数签名所期望的参数。

      ¹实际上不会,因为 OCaml 中 int 的内存表示与 C 中的 int 并不完全相同(但这是另一个 subjet)

      【讨论】:

        猜你喜欢
        • 2012-06-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-09
        • 2014-08-12
        相关资源
        最近更新 更多