【问题标题】:What programming languages have something like Haskell’s `newtype`什么编程语言有类似 Haskell 的 `newtype` 的东西
【发布时间】:2018-12-10 11:31:21
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 我不太明白你关于为什么单值 C 结构不符合条件的论点。显然,如果您使用强制转换,您会失去类型安全性,但如果您只使用记录访问器和构造函数(就像您在 Haskell 中所做的那样),则不会。任何体面的编译器都会将它们内联到相当于强制转换的东西,就像 GHC 对 newtype 所做的那样。
  • leftroundabout:你说得有道理;修改了我的问题。
  • @leftaroundabout 你的表述“任何体面的编译器”让我假设 C 编译器不需要这样做?

标签: haskell functional-programming programming-languages newtype


【解决方案1】:

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

例子:

data Age = Age Int

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

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

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

【讨论】:

  • 感谢弗雷格的指点。关于 PASCAL:这真的需要显式转换吗?他们会是什么样子?
  • @JoachimBreitner 不幸的是,我不太确定,已经有一段时间了,你知道的。但我知道一个人甚至可以有类似type age = 0 .. 130 的东西。也许这有帮助:www2.informatik.uni-halle.de/lehre/pascal/sprache/pas_cast.html,本质上,它是显式类型转换。
  • 实际上,PASCAL 没有标称类型。然而 Oberon、Modula-2 和 ADA 拥有它。所以我会编辑这个。
【解决方案2】:

我相信 Scala 的 value classes 满足这些条件。

例如:

case class Kelvin(k: Double) extends AnyVal

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

【讨论】:

  • 你能创建任何东西的新类型,还是只创建原始类型?
  • 它可以是任何东西,而不仅仅是原始类型。
【解决方案3】:

Go 有这个:

如果我们声明

type MyInt int

var i int
var j MyInt

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

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

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

您不能将 bar 传递给需要 foo (test) 的函数。

【讨论】:

  • 您能否说明将int 分配给j 导致的类型错误?另外,您是否通过电缆将 int 上的函数提升到 MyInt(如数字函数)?
  • @DonStewart: play.golang.org/p/IBGEdJyB5d;不,没有自动“提升”。您必须进行手动转换。不过,像 +- 这样的不是函数的内置函数可以工作。
  • @larsmans:你如何在 MyInt 和 int 之间进行转换?是否只能在这些类型或任何类型之间进行转换(即,即使在错误使用转换的情况下也是类型安全的)?
  • @JoachimBreitner,这是一个例子:play.golang.org/p/Dj8HahN6ex 它看起来很像函数调用。但显然可以将转换应用于不同的类型:play.golang.org/p/nmSVcQvD3a
  • @VladimirMatveev 你能在任意类型之间进行转换,从而导致运行时崩溃吗?那么由于第三个要求,它将不符合条件。
【解决方案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 通常会尽可能地取消装箱。例如

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

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


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

【讨论】:

  • 你说“它可以”。真的吗?此外,转换是什么样子的,它是否优化为 noop?转换后的值是否与原始值共享?
  • @JoachimBreitner 是的,确实如此,是的,转换是无操作的,是的,转换后的值是共享的。我开头说 newtype is 是一种透明的优化。 “它可以”来自我的帖子的一部分,它解释了“newtype”的效果是一组相关优化的一部分,以及为什么可以应用它们。
【解决方案5】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多