17

Haskell 编程语言有一个概念newtypes:如果我写,则创建一个与 同构newtype Foo = Foo (Bar)的新类型,即两者之间存在双射转换。此构造的属性是:FooBar

  • 这两种类型是完全独立的(即编译器不允许您在不使用显式转换的情况下使用另一种类型)。
  • 它们共享相同的表示。特别是,转换函数的运行时间成本为零,并在堆上返回“相同的对象”。
  • 只能在这些类型之间进行转换,不能误用,即保持类型安全。

还有哪些其他编程语言提供此功能?

当仅与记录访问器/构造器一起使用时,一个示例似乎是 C 中的单值结构。当与强制转换一起使用时,无效的候选对象将是 C 中的单值结构,因为编译器不会检查强制转换,或者在 Java 中具有单个成员的对象,因为它们不会共享相同的表示。

相关问题:F# 是否有 Haskell 的“新类型”?(否)并且D 是否有“新类型”?(不再)。

4

5 回答 5

11

不过,弗雷格有这个,不像 Haskell 没有额外的关键字。相反,每个只有一个组件的产品类型都是一种新类型。

例子:

data Age = Age Int

此外,所有具有名义类型并允许根据另一种类型定义类型的语言都应该具有此功能。例如 Oberon、Modula-2 或 ADA。所以之后

type age = integer;      {* kindly forgive syntax errors *}

不能混淆年龄和其他数量。

于 2013-07-11T10:48:19.573 回答
8

Go有这个:

如果我们声明

type MyInt int

var i int
var j MyInt

然后我有 int 类型,j 有 MyInt 类型。变量 i 和 j 具有不同的静态类型,尽管它们具有相同的底层类型,但它们不能在没有转换的情况下相互分配。

“相同的底层类型”意味着 a 在内存中的表示与 aMyInt完全相同int。将 a传递MyInt给期望 an 的函数int是编译时错误。复合类型也是如此,例如在

type foo struct { x int }
type bar struct { x int }

您不能将 a 传递bar给期望foo( test ) 的函数。

于 2013-07-11T11:19:24.753 回答
8

我相信 Scala 的值类满足这些条件。

例如:

case class Kelvin(k: Double) extends AnyVal

编辑:实际上,我不确定转换在所有情况下都具有零开销。本文档描述了一些需要在堆上分配对象的情况,因此我假设在这些情况下,从对象访问底层值会产生一些运行时开销。

于 2013-07-11T10:55:20.313 回答
4

Mercury是一种纯逻辑编程语言,具有类似于 Haskell 的类型系统。

Mercury 中的求值是严格的而不是惰性的,因此 Mercury 的和等价物之间没有语义差异。因此,任何碰巧只有一个构造函数和一个参数的类型都表示为与该参数的类型相同,但仍被视为相同类型;实际上,“newtype”是 Mercury 中的一种透明优化。例子:newtypedata

:- type wrapped
    --->    foo(int)
    ;       bar(string).

:- type wrapper ---> wrapper(wrapped).

:- type synonym == wrapped.

will的表示形式与 of 的表示wrapper相同,wrapped但​​它是一个不同的类型,与之相反,synonym它只是 type 的另一个名称wrapped

Mercury 在其表示中使用标记指针。1由于严格并允许对不同类型有不同的表示,Mercury 通常会尽可能地取消拳击。例如

  • 要引用“类枚举”类型的值(所有空构造函数),您不需要指向任何内存,因此您可以使用整个单词的标记位来说明它是哪个构造函数并将其内联到参考
  • 要引用列表,您可以使用指向 cons 单元格的标记指针(而不是指向本身包含有关它是 nill 还是 cons 单元格的信息的结构的指针)
  • ETC

“新型”优化实际上只是该一般思想的一种特殊应用。“包装”类型不需要分配任何高于已包含“包装”类型的内存单元。并且由于它需要零标签位,它还可以在对“包装”类型的引用中适合任何标签。因此,对“包装”类型的整个引用可以内联到对包装类型的引用中,最终在运行时无法区分。


1此处的详细信息可能仅适用于低级 C 编译等级。Mercury 还可以编译为“高级”C 或Java。显然,Java 中没有任何问题(尽管据我所知“新类型”优化仍然适用),而且我对高级 C 级别的实现细节不太熟悉。

于 2013-07-11T23:44:18.360 回答
4

Rust 一直允许您创建单字段类型,但使用最近稳定的repr(transparent)属性,您现在可以确信创建的类型将具有与包装类型完全相同的数据布局,即使跨 FFI 等。

#[repr(transparent)]
pub struct FooWrapper(Foo);
于 2018-12-10T03:31:21.383 回答