简体   繁体   English

为什么Haskell没有结构类型的记录?

[英]Why does Haskell not have records with structural typing?

I have heard Haskell described as having structural typing. 我听说Haskell被描述为具有结构类型。 Records are an exception to that though as I understand. 据我了解,记录是一个例外。 For example foo cannot be called with something of type HRec2 even though HRec and HRec2 are only nominally different in their fields. 例如,即使HRecHRec2在其字段中名义上不同,也不能使用HRec2类型调用foo

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

foo :: HRec -> Bool

Is there some explanation for rejecting extending structural typing to everything including records? 是否有一些解释拒绝将结构类型扩展到包括记录在内的所有内容?

Are there statically typed languages with structural typing even for records? 是否存在结构类型的静态类型语言,即使是记录? Is there maybe some debate on this I can read about for all statically typed languages in general? 对于所有静态类型语言,我可以阅读一下这个问题吗?

Haskell has structured types, but not structural typing, and that's not likely to change.* Haskell有结构化类型,但不是结构类型,而且不太可能改变。*

The refusal to permit nominally different but structurally similar types as interchangeable arguments is called type safety. 拒绝允许名义上不同但结构相似的类型作为可互换的参数被称为类型安全。 It's a good thing. 那是一件好事。 Haskell even has a newtype declaration to provide types which are only nominally different, to allow you to enforce more type safety. Haskell甚至有一个newtype声明来提供名义上不同的类型,以允许您强制执行更多的类型安全。 Type safety is an easy way to catch bugs early rather than permit them at runtime. 类型安全是一种很容易早期捕获错误的方法,而不是在运行时允许它们。

In addition to amindfv's good answer which includes ad hoc polymorphism via typeclasses (effectively a programmer-declared feature equivalence), there's parametric polymorphism where you allow absolutely any type, so [a] allows any type in your list and BTree a allows any type in your binary tree. 除了amindfv的好答案,包括通过类型类的特殊多态(有效地是程序员声明的特征等价),还有参数多态,你允许绝对任何类型,所以[a]允许列表中的任何类型和BTree a允许任何类型的你的二叉树。

This gives three answers to "are these types interchangeable?". 这给出了“这些类型可以互换吗?”的三个答案。

  1. No; 没有; the programmer didn't say so. 程序员没有这么说。
  2. Equivalent for a specific purpose because the programmer said so. 由于程序员这么说,因此具体用于特定目的。
  3. Don't care - I can do the same thing to this collection of data because it doesn't use any property of the data itself. 不关心 - 我可以对这个数据集做同样的事情,因为它不使用数据本身的任何属性。

There's no 4: compiler overrules programmer because they happened to use a couple of Ints and a String like in that other function. 没有4:编译器取代程序员,因为他们碰巧使用了几个Ints和一个像其他函数一样的String。

*I said Haskell is unlikely to change to structural typing. *我说Haskell不太可能改为结构类型。 There is some discussion to introduce some form of extensible records, but no plans to make (Int,(Int,Int)) count as the same as (Int, Int, Int) or Triple {one::Int, two::Int, three::Int} the same as Triple2 {one2::Int, two2::Int, three2::Int} . 有一些讨论要介绍某种形式的可扩展记录,但没有计划(Int,(Int,Int))计算与(Int, Int, Int)Triple {one::Int, two::Int, three::Int}Triple2 {one2::Int, two2::Int, three2::Int}相同Triple2 {one2::Int, two2::Int, three2::Int}

Haskell records aren't really "less structural" than the rest of the type system. Haskell记录实际上并不比其他类型系统“结构化”。 Every type is either completely specified, or "specifically vague" (ie defined with a typeclass). 每种类型都是完全指定的,或“特别模糊”(即使用类型类定义)。

To allow both HRec and HRec2 to f , you have a couple of options: 要允许HRecHRec2f ,您有两个选择:

Algebraic types: 代数类型:

Here, you define HRec and HRec2 records to both be part of the HRec type: 在这里,您将HRecHRec2记录定义为HRec类型的一部分:

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

foo :: HRec -> Bool

(alternately, and maybe more idiomatic:) (或者,也许更惯用:)

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

Typeclasses 类型类

Here, you define foo as able to accept any type as input, as long as a typeclass instance has been written for that type: 在这里,只要为该类型编写了类型类实例,就可以将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

Using typeclasses allows you to go further than regular structural typing -- you can accept types that have the necessary information embedded in them, even if the types don't superficially look similar, eg: 使用类型类可以让你比常规的结构类型更进一步 - 你可以接受嵌入了必要信息的类型,即使类型表面看起来不相似,例如:

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)

We can run fromInteger (or any arbitrary function) to get the data we need from what we have! 我们可以运行fromInteger (或任意函数)来获取我们所需的数据!

I'm aware of two library implementations of structurally typed records in Haskell: 我知道Haskell中结构类型记录的两个库实现:

HList is older, and is explained in an excellent paper: Haskell's overlooked object system (free online, but SO won't let me include more links) HList比较老,在一篇优秀论文中有解释: Haskell被忽视的对象系统 (免费在线,但SO不会让我包含更多链接)

vinyl is newer, and uses fancy new GHC features. 乙烯基更新,并使用新的GHC功能。 There's at least one library, vinyl-gl, using it. 至少有一个图书馆,即vinyl-gl,使用它。

I cannot answer the language-design part of your question, though. 但是,我无法回答你问题的语言设计部分。

To answer your last question, Go and Scalas definitely have structural typing. 要回答你的上一个问题,Go和Scalas肯定有结构类型。 Some people (including me) would call that "statically unsafe typing", since it implicitly declares all samely- named methods in a program to have the same semantics, which implies "spooky action at a distance", relating code in on source file to code in some library that the program has never seen. 有些人(包括我)会称之为“静态不安全的打字”,因为它隐含地声明程序中所有同名的方法具有相同的语义,这意味着“远距离的幽灵行为”,将源文件中的代码与程序从未见过的某个库中的代码。

IMO, better to require that the same-named methods to explicitly declare that they conform to a named semantic "model" of behavior. IMO,最好要求相同命名的方法明确声明它们符合行为的命名语义“模型”。

Yes, the compiler would guarantee that the method is callable, but it isn't much safer than saying: 是的,编译器会保证该方法是可调用的,但它并不比说:

 f :: [a] -> Int

And letting the compiler choose an arbitrary implementation that may or may not be length . 并让编译器选择可能length可能不length的任意实现。

(A similar idea can be made safe with Scala "implicits" or Haskell (GHC?) "reflection" package.) (使用Scala“implicits”或Haskell(GHC?)“反射”包可以使类似的想法变得安全。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM