参数多态性与Ad-hoc多态性

发布于 2021-01-30 17:48:32

我想了解参数多态性(例如Java / Scala / C
++语言中的通用类/函数的多态性)与Haskell类型系统中的“即席”多态性之间的主要区别。我熟悉第一种语言,但是我从未与Haskell合作。

更确切地说:

  1. 例如Java中的类型推断算法与Haskell中的类型推断有何不同?
  2. 请给我举一个例子,这种情况可以用Java / Scala编写但不能用Haskell编写(根据这些平台的模块化功能),反之亦然。

提前致谢。

关注者
0
被浏览
100
1 个回答
  • 面试哥
    面试哥 2021-01-30
    为面试而生,有面试问题,就找面试哥。

    由于每TAPL,§23.2:

    参数多态性(…),允许使用变量代替实际类型来“通用”键入单个代码,然后根据需要使用特定类型进行实例化。参数定义是统一的:它们的所有实例的行为都相同。(…)

    相比之下,即席多态性使“多态性”值在不同类型下“显示”时表现出不同的行为。即席多态性的最常见示例是重载,该重载将单个功能符号与许多实现相关联。编译器(或运行时系统,取决于重载分辨率是静态的还是动态的)根据参数的类型为函数的每个应用程序选择适当的实现。

    因此,如果考虑历史的连续阶段,非通用官方Java(又名J2SE
    5.0之前的版本
    ,2004年9月之前)具有即席多态性-
    因此您可以重载方法
    -但不能实现参数多态性,因此您不能吨写一个通用的方法。之后,您当然可以两者都做。

    相比之下,自1990年成立以来,Haskell就具有参量多态性,这意味着您可以编写:

    swap :: (A; B) -> (B; A)
    swap (x; y) = (y; x)
    

    其中A和B是类型变量,无需假设即可实例化为 所有 类型。

    但是,没有预先存在的结构可以提供 即席 多态性,该结构旨在让您编写适用于 几种 (但 不是全部) 类型的函数。实现类型类是实现此目标的一种方式。

    它们使您可以描述一个 (类似于Java接口),并提供要为通用类型实现的函数的 类型签名 。然后,您可以注册一些(并且希望有 几个
    )与该类匹配的 实例 。同时,您可以编写一个通用方法,例如:

    between :: (Ord a)  a -> a -> a -> Bool
    between x y z = x ≤ y ^ y ≤ z
    

    其中Ord是定义函数的类(_ ≤ _)。当使用时,将(between "abc" "d" "ghi")静态解析
    以为字符串(而不是整数)选择正确的 实例 -恰好在(Java)方法重载发生的那一刻。

    您可以使用有界通配符在Java中执行类似的操作。但是
    在这方面Haskell和Java之间主要区别在于,只有Haskell可以自动进行字典传递 :在两种语言中,给定Ord T,,b0和的两个实例b1,您可以构建一个函数f,将它们作为参数并为该对生成实例。输入(b0, b1),例如,按字典顺序使用。现在说你被给予了(("hello", 2), ((3, "hi"), 5))。在Java中,你必须记住的实例为stringint,并通过正确的实例(由四个应用程序f才能申请!)between到该对象。Haskell可以运用组合性,并找出如何仅在基础实例和f构造函数的情况下构建正确的实例(当然,这会扩展到其他构造函数)。


    现在,就 类型推断 而言(这可能是一个不同的问题),对于两种语言而言,它都是 不完整的 ,从某种意义上说,您始终可以编写一个 未注释的
    程序,而编译器将无法为其确定程序。方式。

    1. 对于Haskell来说,这是因为它具有强制性(也称为一流)多态性,对于该类型性,无法推断类型。请注意,在这一点上,Java仅限于一阶多态性(Scala扩展的东西)。

    2. 对于Java,这是因为它支持反向子类型化

    但是这些语言的主要区别在于在实践中 类型推断适用的程序语句范围以及 类型推断结果 正确性重要性

    1. 对于Haskell而言,推论适用于所有“非高度多态”术语,并基于已发布的著名算法扩展,努力返回声音结果:

      • Haskell的推理本质上基于Hindley-Milner,当您推断应用程序的类型时, 类型变量 (例如上例中的AB)只能用 非多态 类型实例化(我正在简化,但这实际上是您可以在例如Ocaml中找到的ML风格的多态性。
      • 最近的GHC将确保仅对于具有非Damas-Milner类型的let绑定或λ抽象才需要类型注释。
      • Haskell甚至在其最繁琐的扩展(例如GADT)中都尝试相对保持与这个不可理解的核心的相对距离。无论如何,提议的扩展几乎总是出现在带有扩展类型推断 正确性 证明的论文中。
      • 对于Java,无论如何,类型推断都以 更为有限的方式 适用:

    在Java 5发行之前,Java中没有类型推断。根据Java语言文化, 程序员必须显式声明每个变量,方法和动态分配的对象的类型 。在Java
    5中引入泛型(按类型参数化的类和方法)后, 该语言保留了对变量,方法和分配的这一要求
    。但是多态方法(按类型参数化)的引入要求(i)程序员在每个多态方法调用站点提供方法类型参数,或者(ii)语言支持方法类型参数的推断。为了避免给程序员带来额外的文书负担,Java
    5的设计人员选择执行类型推断来确定类型参数 用于多态方法调用
    。(来源,重点是我的)

    推理算法基本上是GJ的,但有几分
    缺憾此外通配符作为一种事后(请注意,我不是最新的在J2SE
    6.0所做的可能修正,虽然)。方法上的巨大概念差异是Java的推断是 局部的
    ,从某种意义上说,表达式的推断类型仅取决于从类型系统生成的约束及其子表达式的类型,而不取决于上下文。

    请注意,关于类型不完整(有时甚至是错误的类型推断)的聚会线相对宽松。根据规格

    还要注意,类型推断不会以任何方式影响健全性。如果推断的类型是荒谬的,则调用将产生类型错误。类型推断算法应被视为一种启发式算法,旨在在实践中很好地进行表现。如果无法推断出预期的结果,则可以使用显式类型参数。



推荐阅读
知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看