как ограничить параметр только одним вариантом типа варианта - PullRequest
2 голосов
/ 06 ноября 2019

Предположим, у меня есть два типа, Vector2D и Vector3D, и они помечены (это правильный термин, верно?), И я хочу написать функцию, которая работает ТОЛЬКО на Vector2D (или vector(two)более правильно здесь?), вот так:

type two;
type three;
type vector('a) = 
  | Vector2D(float, float): vector(two)
  | Vector3D(float, float, float): vector(three);

let first = (a: vector(two)) => {
  switch(a) {
    | (x, y) => x +. y
  }
}

let second = (Vector2D(x, y)) => {
  x +. y
}

let third = ((x, y): vector(two)) => {
  x +.y
}

Функции first и second запрещают передавать Vector3D, как я хочу, но выдают предупреждение "Это сопоставление с образцом не является исчерпывающим».

Для first Я хотел бы знать, почему это не является исчерпывающим, разве я не ограничил возможные варианты Vector2D здесь? Для second Полагаю, причина та же, что и для first, но как можно решить проблему с этим синтаксисом?

Что касается third, этот код не компилируется, посколькуMsgstr "Этот шаблон соответствует ('a,' b), но вектор (два) ожидался". Почему компилятор ожидает здесь какой-либо кортеж? Разве невозможно использовать деструктуризацию в параметрах функции?

РЕДАКТИРОВАТЬ:
Оказывается, есть еще более простая проблема, чтобы продемонстрировать, что я хочу

type state = Solid | Liquid
let splash = (object) => {
  switch(object) {
    | Liquid => "splashing sounds. I guess."
    | Solid => "" // this should not even be possible in the context of this function, and I want to compiler to enforce this
}

Ответы [ 2 ]

3 голосов
/ 06 ноября 2019

Что касается части GADT, проблема здесь в том, что вы используете bucklescript и его древнюю версию компилятора 4.02.3. Например, следующий код прекрасно работает в OCaml ≥ 4.03:

type two = Two ; 
type three = Three ;
/* Never define abstract types when you want to use them as type level tags,
  your code will only works inside the module defining them, and then
  fail due to injectivity problems.
*/

type vector('a) = 
  | Vector2D(float, float): vector(two)
  | Vector3D(float, float, float): vector(three);

let sum_2 = (Vector2D(x,y)) => x +. y
let sum_3 = (Vector3D(x,y,z)) => x +. y +. z 
let sum_any = (type a, x: vector(a) ) => switch (x) {
  | Vector2D(x,y) => x +. y;
  | Vector3D(x,y,z) => x +. y +. z
}

Но он не сработает 4.02.3 с предупреждением об исчерпанности (что следует рассматривать как ошибку), потому что проверка исчерпываемости для GADTс пунктами опровержения был добавлен только в 4.03.

1 голос
/ 06 ноября 2019

Вы можете выполнить то, что хотите, используя полиморфные варианты :

type vector = [
  | `Vector2D(float, float)
  | `Vector3D(float, float, float)
];

let first = (a: [`Vector2D(float, float)]) => {
  switch(a) {
    | `Vector2D(x, y) => x +. y
  }
}

let second = (`Vector2D(x, y)) => {
  x +. y
}

Это даст вам ошибку типа, если вы попытаетесь передать `Vector3D любой из функций:

               ^^^^^^^^^^^^^^^^^^^^^
Error: This expression has type [> `Vector3d(float, float, float) ]
       but an expression was expected of type [< `Vector2D(float, float) ]
       The second variant type does not allow tag(s) `Vector3d

Обратите внимание, что Vector2D и Vector3D в вашем примере не являются типами. Они являются конструкторами, которые создают значения типа vector('a). Как вы правильно заметили, их иногда также называют «тегами», а тип иногда называют «теговым объединением» вместо «варианта», потому что значения содержат тег, который указывает, какой конструктор использовался для его создания. Этот тег проверяется при сопоставлении с шаблоном с использованием шаблона конструктора.

То, что вы используете в своем примере, является не обычным вариантом, а Обобщенным алгебраическим типом данных (GADT) (который мог бы быть более удачно назван «Обобщенный вариант» в OCaml / Reason), , и причина, по которой он не работает, заключается в том, что, хотя GADT позволяет назначать конкретный тип конструктору, он не работает с другимнаоборот. Например, у вас может быть несколько конструкторов, которые указывают vector(two). (Редактировать: Это кажется неправильным, см. Ответ @ octachron)

Ни одна из функций first или third в вашем примере не компилируется, Компилятор ожидает кортеж, потому что вы используете паттерн кортежа вместо паттерна конструктора. (x, y) не совпадает с Vector2D(x, y). Если бы это было так, вы не смогли бы отличить Vector2D(float, float) от Line(point, point), например, который содержит данные совершенно другого типа.

...