Почему поток не может более точно определить тип в этом примере функционала Either?

У меня есть код, который играет с некоторыми функциональными концепциями javascript, такими как Eithers, представленный Брайаном Лонсдорфом в эта серия.

Я пытался ввести выражение Either. Вот моя попытка:

type RightApi<X> = {
  map: <C: *>(f: X => C) => RightApi<C>,
  fold: <C: *>(f: Function, g: X => C) => C
};

const Right = <X: *>(x: X): RightApi<X> => ({
  map: f => Right(f(x)),
  fold: (f, g) => g(x),
})

type LeftApi<X> = {
  map: (f: Function) => LeftApi<X>,
  fold: <C: *>(f: X => C, g: Function) => C,
};

const Left = <X: *>(x: X): LeftApi<X> => ({
  map: f => Left(x),
  fold: (f, g) => f(x),
});

const fromMayBeNullOrUndefined = <X: *>(x: X) =>
  ((x === null || x === undefined) ? Left(x) : Right(x));

const test = fromMayBeNullOrUndefined(3).fold(x => null, x => x);
// null | number

ссылка на сайт

К сожалению, это не совсем так, как хотелось бы. В последней строке я пытаюсь проверить набор текста. Логически мне кажется очевидным, что test будет number. Поскольку значение, предоставленное для fromMayBeNullOrUndefined, равно 3, то есть не равно null или undefined, поэтому при вызове fold должна быть вызвана правая функция.

Я здесь ошибся? Или поток не может более точно определить результат?

Действительно ли Flow пытается выполнить динамический анализ в подобном случае?

Pointy 05.09.2018 21:13

Хороший вопрос. Насколько я понимаю, это так. Такой синтаксис <X: *>(x: X) => x будет чувствителен к аргументам, с которыми вызывается функция. Но, глядя на этот простой пример, я думаю, что то, что он делает, скорее рассматривает все возможности с учетом предоставленных аргументов.

fraxture 05.09.2018 21:37
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
2
49
3

Ответы 3

Or is it not possible for flow to infer the result more precisely?

Вывод типа останавливается на границах функции. Было бы слишком хрупко, если бы тело одной функции могло повлиять на проверку типов в другой. Единственный раз, когда рассматривается фактическое тело функции, - это проверка типа этой функции на соответствие ее собственному типу.

Итак, хотя, да, вы можете заглянуть внутрь fromMayBeNullOrUndefined и знать, что результат Left или Right зависит от того, является ли xnull / undefined или нет, неразумно ожидать, что Flow сделает то же самое. Это также нарушило бы абстракцию функции, если бы вам пришлось заглянуть в исходный код, чтобы узнать, как выполнить проверку типа.

Похоже, то, что вы ищете, - это одна из двух вещей, которые теперь может предоставить Flow.

  1. %checks a.k.a. Предикатные функции

  2. $NonMaybeType<T>, то есть все поставляемые типы, кроме null и undefined.

Кроме того, типы Function в Flow - это рассматривается как "небезопасный", поэтому более конкретное определение типа поможет.

В случае map() и fold() вы можете захотеть изучить типа $Call<T>, который позволяет вам вызывать функцию для типа статически во время автор.

Надеюсь это поможет.

Упомянутая вами проблема может быть решена путем определения различных сигнатур типа для функции fromMaybeOrUndefined следующим образом:

declare function fromMayBeNullOrUndefined(x: null | void): LeftApi<null | void>;
declare function fromMayBeNullOrUndefined<X>(x: X): RightApi<X>;
function fromMayBeNullOrUndefined<X>(x: X) {
  if (x === null || x === undefined) {
    return Left(x);
  }
  return Right(x);
}

Теперь проверьте правильность проверки типов:

const test: null = fromMayBeNullOrUndefined(null).fold(x => null, x => x);

const test: number = fromMayBeNullOrUndefined(3).fold(x => null, x => x);

Но, хотя это забавное упражнение, в нем полностью отсутствует смысл типа Either:

Суть Either в том, что Left и Right имеют один и тот же API, так что во время выполнения базовое значение (или часто называемые данными) может быть или из них. Независимо от того, является ли значение Left или Right, прозрачно. Это важно, потому что вы должны использовать тип Either (или на самом деле любую монаду), когда есть какое-то внешнее влияние, которое может иметь два результата. Например, вы хотите подключиться к базе данных - соединение может быть создано или или оно не работает. Или в вашем случае пользователь какого-либо API может предоставить или число или нулевое значение.

/* @flow */
type Either<L, R> = {
  map: <T>(f: R => T) => Either<L, T>,
  fold: <T>(f: L => T, g: R => T) => T
};

const Right = <R>(x: R): Either<any, R> => ({
  map: <T>(f: R => T): Either<any, T> => Right(f(x)),
  fold: <T>(f: any => T, g: R => T): T => g(x),
})

const Left = <L>(x: L): Either<L, any> => ({
  map: <T>(f: any => T): Either<any, T> => Left(x),
  fold: <T>(f: L => T, g: any => T): T => f(x),
});

const fromMayBeNullOrUndefined = <X>(x: X): Either<null | void, X> =>
  ((x === null || x === undefined) ? Left(x) : Right(x));

const test = fromMayBeNullOrUndefined(3).fold(x => null, x => x);
(test: null | number)

С Either вы можете продолжать кодирование в «счастливом пути» (справа) до тех пор, пока не захотите снова сложить пути выполнения. Пример:

function getAccountBalance(name: string): string {
  const account: Either<Error, Account> = getAccountByCustomerName(name);

  return account
    .map(account => account.balance)
    .fold(
      () => "Seems like you don't have an account at our service",
      balance => `Your balance is ${balance}`,
    );
}

Этот пример станет более интересным, когда будет выполняться несколько операций, как описано в следующей видеолекции.

Другие вопросы по теме