【问题标题】:Why does Haskell not have records with structural typing?为什么 Haskell 没有结构类型的记录?
【发布时间】:2014-01-12 05:22:07
【问题描述】:

我听说 Haskell 被描述为具有结构类型。据我了解,记录是一个例外。例如,foo 不能用 HRec2 类型的东西调用,即使 HRecHRec2 在它们的字段上只是名义上不同。

data HRec = HRec { x :: Int, y :: Bool }
data HRec2 = HRec2 { p :: Int, q :: Bool }

foo :: HRec -> Bool

是否有一些解释拒绝将结构类型扩展到包括记录在内的所有内容?

是否存在具有结构类型的静态类型语言,即使对于记录也是如此?对于所有静态类型的语言,我是否可以阅读有关这方面的一些争论?

【问题讨论】:

  • 这似乎更像是一个邮件列表问题,而不是一个 SO 问题——研究语言演变历史和基本原理并不是真正的编程问题。但我并没有真正致力于这个想法。
  • 我不会将记录视为例外。 Haskell 的打字通常是非常名义上的,而不是结构性的。
  • 在有人为此问题提供“结构类型”的精确定义之前,这个问题没有意义。我所知道的结构类型的唯一意义是与函数的松散类比:(a -> b -> Bool) -> [a] -> b -> Bool 类型的函数接受任何匹配的函数作为其(第一个)参数。但这不是人们通常所说的“结构”或“鸭子”打字的意思。
  • OCaml 具有结构类型的记录,它不认为这些记录类型在结构上是等效的。您可以说这是因为它将字段名称视为记录类型结构的一部分。 (这也是 OCaml 在其对象系统中处理结构等价的方式:名称很重要。)

标签: haskell record structural-typing


【解决方案1】:

Haskell 有结构化类型,但没有结构化类型,这不太可能改变。*

拒绝允许名义上不同但结构相似的类型作为可互换的参数被称为类型安全。那是一件好事。 Haskell 甚至有一个 newtype 声明来提供只是名义上不同的类型,以允许您强制执行更多类型安全。类型安全是一种尽早发现错误的简单方法,而不是在运行时允许它们。

除了 amindfv 的好答案(包括通过类型类的临时多态性(实际上是程序员声明的功能等效性))之外,还有参数多态性,您绝对允许任何类型,因此 [a] 允许列表中的任何类型和 BTree a允许二叉树中的任何类型。

这给出了“这些类型是否可以互换?”的三个答案。

  1. 不;程序员没有这么说。
  2. 由于程序员这样说,因此用于特定目的。
  3. 不关心 - 我可以对这个数据集合做同样的事情,因为它不使用数据本身的任何属性。

没有 4:编译器推翻了程序员,因为他们碰巧在其他函数中使用了几个 Int 和一个 String。

*我说过 Haskell 不太可能改用结构化类型。有一些讨论会介绍某种形式的可扩展记录,但没有计划让(Int,(Int,Int))(Int, Int, Int)Triple {one::Int, two::Int, three::Int}Triple2 {one2::Int, two2::Int, three2::Int} 相同。

【讨论】:

  • 当你连续写了三个冗长的 cmets 时,是时候承认你可能正在回答。转换为答案。
  • 这三个答案是我读过的关于单态性和有界/参数多态性的最好的通俗化描述。谢谢!
  • 决定两种类型是否相等的是数学,而不是程序员。 2 == 两个,不管程序员怎么想。
【解决方案2】:

Haskell 记录实际上并不比类型系统的其他部分“结构更少”。每种类型要么完全指定,要么“特别模糊”(即使用类型类定义)。

要同时允许 HRecHRec2f,您有几个选择:

代数类型:

在这里,您将 HRecHRec2 记录定义为 HRec 类型的一部分:

data HRec = HRec  { x :: Int, y :: Bool }
          | HRec2 { p :: Int, q :: Bool }

foo :: HRec -> Bool

(或者,也许更惯用:)

data HRecType = Type1 | Type2
data HRec = HRec { hRecType :: HRecType, x :: Int, y :: Bool }

类型类

在这里,您将foo 定义为能够接受任何类型作为输入,只要已为该类型编写了类型类实例:

data HRec  = HRec  { x :: Int, y :: Bool }
data HRec2 = HRec2 { p :: Int, q :: Bool }

class Flexible a where
   foo :: a -> Bool

instance Flexible HRec where
   foo (HRec a _) = a == 5 -- or whatever

instance Flexible HRec2 where
   foo (HRec2 a _) = a == 5

使用类型类可以让你比常规的结构类型更进一步——你可以接受嵌入了必要信息的类型,即使这些类型表面上看起来并不相似,例如:

data Foo = Foo { a :: String, b :: Float }
data Bar = Bar { c :: String, d :: Integer }

class Thing a where
   doAThing :: a -> Bool

instance Thing Foo where
    doAThing (Foo x y) = (x == "hi") && (y == 0)

instance Thing Bar where
    doAThing (Bar x y) = (x == "hi") && ((fromInteger y) == 0)

我们可以运行fromInteger(或任何任意函数)来从我们现有的数据中获取我们需要的数据!

【讨论】:

  • 我认为这不能回答问题。
  • @luqui - 要回答这个问题,我想你只需要说“haskell 没有结构类型”。我添加了解决方案来向 OP 展示在 haskell 中获得相同效果的惯用方法
【解决方案3】:

我知道 Haskell 中结构类型记录的两个库实现:

HList 较旧,并在一篇出色的论文中进行了解释:Haskell 的被忽视的对象系统(免费在线,但不允许我包含更多链接)

vinyl 较新,并使用了新奇的 GHC 功能。至少有一个库,vinyl-gl,在使用它。

不过,我无法回答您问题的语言设计部分。

【讨论】:

    【解决方案4】:

    要回答您的最后一个问题,Go 和 Scalas 肯定具有结构类型。有些人(包括我)会称之为“静态不安全类型”,因为它隐式声明程序中所有同名的方法具有相同的语义,这意味着“远处的诡异动作”,将源文件中的代码与程序从未见过的某些库中的代码。

    IMO,最好要求同名方法明确声明它们符合命名语义“模型”的行为。

    是的,编译器会保证该方法是可调用的,但并不比说:

     f :: [a] -> Int
    

    并让编译器选择一个任意实现,它可能是也可能不是length

    (使用 Scala“隐式”或 Haskell(GHC?)“反射”包可以使类似的想法变得安全。)

    【讨论】:

      猜你喜欢
      • 2016-04-16
      • 2020-08-30
      • 1970-01-01
      • 2014-12-25
      • 1970-01-01
      • 2011-06-03
      • 2019-09-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多