Тип охранника с дополнением в ложной ветке - PullRequest
4 голосов
/ 07 июня 2019

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

interface Model { side: 'left' | 'right'; }
interface LeftModel { side: 'left'; }
interface RightModel { side: 'right'; }
type Either = LeftModel | RightModel;

function isLeft(value: Either): value is LeftModel { // else is RightModel
  return value.side === 'left';
}

Это кажется невозможным, по крайней мере, не так, как я это делаю. Typescript рад заключить, что Either может быть моделью, но не принимает, что Model может быть Either. Это дает ошибку:

declare const model: Model;
isLeft(model) // ts(2345)

Эта проблема принципиально неразрешима?

Если нет, то как заставить фальшивую ветвь сузиться до дополнения?

см. Полный пример на этой Площадке машинописного текста

EDIT

Из этого наивного примера видно, что Model и Either эквивалентны, но это, вероятно, не может быть обобщено. Я мог бы добиться некоторого прогресса, объединив охрану двух типов в попытке сообщить системе типов, что Model действительно является действительным Either (см. Эту новую Playground ). Это, однако, оставляет меня с нежелательной ветвью (см. Строку 22), поэтому не вполне удовлетворительно.

Есть ли способ сообщить системе типов, что Either и Model строго эквивалентны?

Мне не нужно особо использовать ни охранники типов, ни объединяемые типы, это была моя первая попытка решить проблему, но она создала собственные проблемы. Объединение типов будет жизнеспособным только в том случае, если мы сможем гарантировать, что объединение суженного типа и его относительного дополнения действительно эквивалентно суженному типу. Это зависит от системы типов, имеющей понимание дополнения, что может быть не так (пока). См. поиск дополнений в машинописи и справочник по типам утилит

Кто-то предложил использовать fp-ts и / или monocle-ts , чтобы решить эту проблему, но некоторые из этих концепций функционального программирования все еще идут мне в голову. Если кто-то знает, как применять их здесь, хотя это было бы хорошо. Либо звучит так, как будто это может помочь здесь ...

Ответы [ 2 ]

2 голосов
/ 07 июня 2019

Оператор объединения | не выполняет объединение типов свойств типов, указанных в объединении.

т.е.

type Either = LeftModel | RightModel === { side: 'left ' } | { side: 'right' } 
           !== { side: 'left' | 'right' } === Model

Either строгообъединение LeftModel и RightModel не объединение типа side и side

Выдержка из ошибки, я думаю, этим все сказано,

АргументТип «Модель» не может быть назначен параметру типа «Любой».Тип «Модель» нельзя назначить типу «RightModel».Типы собственности "сторона" несовместимы.Тип "слева" |«право» не может быть присвоено типу «право».Тип "left" не может быть назначен типу "right".

1 голос
/ 07 июня 2019

Вы можете сделать это с объединением типов и без охраны типов:

interface Model { side: 'left' | 'right'; }
interface LeftModel extends Model { side: 'left'; }
interface RightModel extends Model { side: 'right'; }

function whatever(model: LeftModel | RightModel) {
  model.side // left or right

  if (model.side === 'left') {
    model.side; // left - model is also LeftModel at this point
  } else {
    model.side; // right - model is a RightModel
  }
}

Playground

Наследование от Model на самом деле необязательно,так как здесь действительно работает объединение типов.Но это помогает ограничить любые подклассы только «правым» или «левым».

Надеюсь, это тот тип вещей, который вы пытаетесь выполнить?

Хотя охранник типов не являетсянеобходимо, это все еще может быть использовано.Он не может определить дополнительный тип RightModel, но вызывающий может, уже имея value, ограниченный объединением LeftModel и RightModel.

interface Model { side: 'left' | 'right'; }
interface LeftModel extends Model { side: 'left'; }
interface RightModel extends Model { side: 'right'; }

function isLeft(value: Model): value is LeftModel {
  return value.side === 'left';
}

function whatever(model: LeftModel | RightModel) { // This union makes the else branch work
  model.side // left or right

  if (isLeft(model)) {
    model.side; // left - model is also LeftModel at this point
  } else {
    model.side; // right - model is a RightModel
  }
}

стип охранник

...