什么编程语言有类似Haskell的`newtype`

时间:2013-07-11 10:41:56

标签: haskell functional-programming programming-languages newtype

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

  • 这两种类型是完全独立的(即编译器不允许您使用另一种类型而不使用显式转换)。
  • 他们有相同的表现形式。特别是,转换函数的运行时成本为零,并在堆上返回“相同的对象”。
  • 只能在这些类型之间进行转换,并且不能被误用,即保留类型安全性。

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

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

相关问题:Does F# have 'newtype' of Haskell?(否)和Does D have 'newtype'?(不再是)。

5 个答案:

答案 0 :(得分:11)

但是,

Frege有这个,与Haskell不同,没有额外的关键字。相反,只有一个组件的每种产品类型都是新类型。

示例:

data Age = Age Int

此外,所有具有标称类型且允许根据另一种类型定义类型的languga都应该具有此功能。例如Oberon,Modula-2或ADA。所以

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

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

答案 1 :(得分:8)

我相信Scala的value classes符合这些条件。

例如:

case class Kelvin(k: Double) extends AnyVal

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

答案 2 :(得分:7)

Go有这个:

  

如果我们宣布

type MyInt int

var i int
var j MyInt
     

然后我有类型int,j有类型MyInt。变量i和j具有不同的静态类型,虽然它们具有相同的基础类型,但如果没有转换,它们就无法相互分配。

“相同的基础类型”意味着MyInt的内存中的表示正好是int的表示。将MyInt传递给期望int的函数是编译时错误。对于复合类型也是如此,例如之后

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

您无法将bar传递给期望footest)的函数。

答案 3 :(得分:4)

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

Mercury中的评估是严格的而不是懒惰的,因此Mercury等同于newtypedata之间不存在语义差异。因此,恰好只有一个构造函数只有一个参数的任何类型都与该参数的类型相同,但仍被视为相同的类型;有效的“newtype”是Mercury中的透明优化。例如:

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

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

:- type synonym == wrapped.

wrapper的表示形式与wrapped的表示形式相同,但它是一种不同的类型,而synonym只是wrapped类型的另一个名称。

Mercury在其表示中使用标记指针。 1 严格并允许对不同类型使用不同的表示,Mercury通常会尝试尽可能地取消拳击。 e.g。

  • 要引用“enum-like”类型的值(所有nullary构造函数),您不需要指向任何内存,因此您可以使用整个单词的标记位来说明它是哪个构造函数和内联在参考文献中
  • 要引用列表,您可以使用指向cons单元格的标记指针(而不是指向结构的指针,该结构本身包含有关是否为nill或cons单元格的信息)

“newtype”优化实际上只是该一般概念的一个特定应用。 “包装器”类型不需要在已经保持“包装”类型的上方分配任何存储器单元。由于它需要零标签位,因此它也可以适应“包装”类型的引用中的任何标签。因此,对“wrapped”类型的整个引用可以内联到对包装器类型的引用中,最终在运行时无法区分。


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

答案 4 :(得分:2)

Rust始终允许您创建单字段类型,但是借助最近稳定的repr(transparent)属性,您现在可以确信创建的类型将具有与包装类型完全相同的数据布局,即使在FFI和这样的。

#[repr(transparent)]
pub struct FooWrapper(Foo);