Алгебраические типы данных в TypeScript

Другими словами:

Как бы вы набрали свойство windowState DOM в TypeScript?

РЕШЕНО (в TypeScript 2):

declare var windowState: WindowState
const enum WindowState {
  STATE_MAXIMIZED = 1,
  STATE_MINIMIZED = 2,
  STATE_NORMAL = 3,
  STATE_FULLSCREEN = 4
}
...
var windowState = 5 // Type Error, as expected!

Исходный вопрос:

Как мне declare type в TypeScript, чтобы он описывал алгебраический тип данных? Целью этого является описание существующего API.

Когда я пробую следующее, TypeScript явно жалуется, что type is expected:

type Weather = 'sunny' | 'bad'

Одна из моих идей - использовать JavaScript 2015 Symbol, однако TypeScript, похоже, не знает об этом.

Другая идея заключалась в использовании enum, однако TypeScript жалуется, что member initializer must be constant expression:

const enum Weather {
  sunny = 'sunny',
  bad = 'bad',
  windy = Symbol('windy')
}

Я бы подумал, что константа string - это постоянное выражение.


person ᆼᆺᆼ    schedule 25.11.2015    source источник
comment
В этом конкретном примере вы, вероятно, могли бы использовать enum (хотя местами необходимо преобразовать в / из String)   -  person Thilo    schedule 25.11.2015
comment
Да, я тоже добавлю это к вопросу, поскольку enum не совсем удовлетворительный   -  person ᆼᆺᆼ    schedule 25.11.2015
comment
TypeScript понимает символы. Если ваша среда выполнения поддерживает это, вы можете либо загрузить lib.es6.d.ts, либо реплицировать интерфейс Symbol.   -  person Kitson    schedule 25.11.2015
comment
@Kitson Считает ли он их постоянным выражением?   -  person ᆼᆺᆼ    schedule 25.11.2015
comment
@CodeiSir И Symbols?   -  person ᆼᆺᆼ    schedule 25.11.2015
comment
TypeScript 1.8 представит типы строковых литералов: github.com/Microsoft/TypeScript/pull/5185   -  person CoderPi    schedule 07.12.2015
comment
Замечательно, это не полная поддержка алгебраических типов данных, но именно то, что мне нужно для моей цели. Спасибо. Почему бы не превратить ваш комментарий в ответ?   -  person ᆼᆺᆼ    schedule 07.12.2015
comment
У вас неправильный фрагмент кода haskell. Правая часть объявления данных должна содержать конструкторы значений, а не значения.   -  person pedrofurla    schedule 15.12.2016
comment
@pedrofurla Хорошо, извините, как правильно выразить этот тип в Haskell?   -  person ᆼᆺᆼ    schedule 16.12.2016
comment
Стало ли работать с алгебраическими типами данных более естественным с тех пор, как много лет назад?   -  person Alex Gordon    schedule 02.11.2020


Ответы (2)


TypeScript 2.0 поддерживает размеченные объединения / алгебраические типы данных. Документация находится здесь.

Вы можете комбинировать строковые литералы, типы объединений, средства защиты типов и псевдонимы типов для создания расширенного шаблона, называемого размеченными объединениями, также известного как тегированные объединения или алгебраические данные типы. Дискриминационные союзы полезны в функциональном программировании. Некоторые языки автоматически определяют профсоюзы за вас; TypeScript вместо этого основывается на шаблонах JavaScript в том виде, в каком они существуют сегодня. Всего три ингредиента:

  1. Типы, которые имеют общее свойство литерала (или перечисления) дискриминант.
  2. Псевдоним типа, который принимает объединение этих типов в union.
  3. Типа охрана на общем имуществе.

Давайте начнем:

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}

Сначала мы объявляем интерфейсы, которые мы объединяем. Каждый интерфейс имеет свойство kind с различным типом строкового литерала. Свойство kind называется дискриминантом или тегом. Остальные свойства специфичны для каждого интерфейса. Обратите внимание, что интерфейсы в настоящее время не связаны. Объединим их в союз:

type Shape = Square | Rectangle | Circle;

Теперь воспользуемся размеченным объединением:

function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
    }
}

В каждой из этих веток TypeScript сузит тип. Если вы попытаетесь использовать предложение case со значением, которого нет ни в каком свойстве kind, TypeScript выдаст ошибку.

person Daniel Rosenwasser    schedule 11.10.2016
comment
Можете ли вы привести пример, как вызвать такую ​​функцию с буквальным аргументом этого типа? Думаю, меня смущают ложные аналогии с синтаксисом Haskell. - person klos; 11.11.2018

Чтобы использовать перечисление, вы должны написать что-нибудь вот так:

enum Weather {
    Sunny,
    Windy
};

let currently = Weather.Sunny;

Строковые литералы

Другой способ, которым OP просит решить эту проблему, прибывает в TypeScript 1.8.

Они позволят вам сделать что-то вроде этого:

type Weather = 'Sunny' | 'Windy';
person Kitson    schedule 25.11.2015
comment
Но мне нужна type, которая может описывать как одну из нескольких string констант. - person ᆼᆺᆼ; 25.11.2015
comment
зачем тебе строковые константы? - person CoderPi; 25.11.2015
comment
Существует существующий API, и это будет типизация для него, - person ᆼᆺᆼ; 25.11.2015
comment
То, о чем вы просите, входит в TypeScript 1.8 (который в настоящее время доступен для тестирования через npm install typescript@next - person Kitson; 07.12.2015