Какие языки программирования имеют что-то вроде Haskells `newtype`

Язык программирования Haskell имеет концепцию newtypes: если я пишу newtype Foo = Foo (Bar), то создается новый тип Foo, который изоморфен Bar, то есть есть взаимно однозначные преобразования между ними. Свойства этой конструкции:

  • Два типа полностью разделены (т.е. компилятор не позволит вам использовать тот, где ожидается другой, без использования явных преобразований).
  • Они имеют одинаковое представление. В частности, функции преобразования имеют нулевую временную стоимость и возвращают "тот же объект" в кучу.
  • Конверсия возможна только между этими типами и не может быть использована неправильно, т.е. сохраняется безопасность типа.

Какие другие языки программирования предоставляют эту функцию?

Один пример, похоже, является однозначной структурой в C, когда используется только с элементами доступа к записи/конструкторами. Недействительные кандидаты были бы однозначными -структурами в C, когда они использовались с кастами, поскольку приведение не проверяется компилятором или объектами с одним членом на Java, поскольку они не будут иметь одинаковое представление.

Похожие вопросы: Есть ли у F # новый тип Haskell? (Нет) и Есть ли у D новый тип? (не более).

Ответы

Ответ 1

Frege имеет это, хотя, в отличие от Haskell, нет лишнего ключевого слова. Вместо этого каждый тип продукта с одним компонентом является новым типом.

Пример:

data Age = Age Int

Кроме того, все функции, которые имеют номинальную типизацию и позволяют определять тип в терминах другого, должны иметь эту функцию. Например, Oberon, Modula-2 или ADA. Итак, после

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

нельзя было путать возраст и некоторую другую величину.

Ответ 2

Я полагаю, что классы значений удовлетворяют этим условиям.

Например:

case class Kelvin(k: Double) extends AnyVal

Изменить: на самом деле, я не уверен, что конверсии имеют нулевые накладные расходы во всех случаях. В этой документации описаны некоторые случаи, когда требуется выделение объектов в куче, поэтому я предполагаю, что в таких случаях для доступа к базовому значению от объекта будут некоторые служебные данные во время выполнения.

Ответ 3

Go имеет следующее:

Если мы объявим

type MyInt int

var i int
var j MyInt

то я имеет тип int и j имеет тип MyInt. Переменные я и j имеют разные статические типы и, хотя они имеют один и тот же базовый тип, они не могут быть назначены друг другу без преобразования.

"Тот же самый базовый тип" означает, что представление в памяти MyInt является точно таким же, как и для int. Передача MyInt функции, ожидающей int, является ошибкой времени компиляции. То же самое верно для композитных типов, например. после

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

вы не можете передать bar функции, ожидающей foo (test).

Ответ 4

Mercury - это чистый логический язык программирования с системой типов, подобной системе Haskell.

Оценка в ртути строгая, а не ленивая, поэтому не было бы семантической разницы между эквивалентами ртути newtype и data. Следовательно, любой тип, который имеет только один конструктор с одним аргументом, представляется идентично типу этого аргумента, но все же рассматривается как один и тот же тип; Эффект "newtype" - это прозрачная оптимизация в Mercury. Пример:

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

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

:- type synonym == wrapped.

Представление wrapper будет идентично представлению wrapped, но это отдельный тип, в отличие от synonym, который является просто другим именем для типа wrapped.

Меркурий использует помеченные указатели в своих представлениях. 1 Будучи строгим и позволяющим иметь разные представления для разных типов, Меркурий обычно пытается покончить с боксом, где это возможно. например.

  • Чтобы ссылаться на значение типа "enum-like" (все конструкторы с нулевым числом), вам не нужно указывать на какую-либо память, чтобы вы могли использовать целое слово бит бита, чтобы сказать, какой конструктор он и встроен что в ссылке
  • Чтобы ссылаться на список, вы можете использовать тег с указателем на ячейку cons (а не указатель на структуру, которая сама содержит информацию о том, является ли она нилой или ячейкой cons)
  • и т.д.

Оптимизация "нового типа" - это действительно одно конкретное приложение этой общей идеи. Тип "обертка" не нуждается в ячейках памяти, выделенных выше того, что уже держит "завернутый" тип. И так как ему нужны нулевые биты тегов, он также может вставлять любые теги в ссылку на "завернутый" тип. Поэтому вся ссылка на "завернутый" тип может быть встроена в ссылку на тип обертки, которая в процессе выполнения становится неотличимой.


1 Детали здесь могут применяться только к классам компиляции низкого уровня. Меркурий также может скомпилироваться с "высоким уровнем" C или Java. Очевидно, что в Java нет бит-скриптов (хотя, насколько я знаю, оптимизация "newtype" по-прежнему применяется), и я чуть менее знаком с деталями реализации в классах высокого уровня C.

Ответ 5

Rust всегда позволял вам создавать однополевые типы, но с недавно стабилизированным атрибутом repr(transparent) вы теперь можете быть уверены, что созданный тип будет иметь точную компоновку данных в виде обернутого типа, даже в FFI и так далее.

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