Можем ли мы сделать тип функций зависимым (производным от?) От его типов функций (при вызове)? - PullRequest
1 голос
/ 03 февраля 2020

Скажем, у нас есть этот сложный набор функций, который:

    interface Succes<a> {
      kind: 'succes'
      value: a
    }
    interface Failure<e> {
      kind: 'failure'
      error: e
    }

    type Result<a, e> = Succes<a> | Failure<e>

    const unit = <a>(a:a): Succes<a> => ({kind: 'succes', value: a})
    const fail = <e>(e:e): Failure<e> => ({kind: 'failure', error: e})

    interface fun<a, b> { (a: a): b }

    const map = <a,b, e>(f: fun<a, b>): fun<Result<a,e>, Result<b,e>> => r => 
      r.kind == 'succes' ? unit(f(r.value)) : r

    const join = <a, e>(r:Result<Result<a, e>, e>): Result<a, e> => 
      r.kind == 'failure' ? r : r.value

    const then = <a, b, e>(f:fun<a, Result<b, e>>) => (r:Result<a, e>) => 
        join(map(f)(r))

    const railRoad = <a, e>(r: Result<a, e>) => ({
      map: <b>(f: (a:a) => b) => railRoad<b, e>(map<a, b, e>(f)(r)),
      then: <b>(f: (a:a) => Result<b,e>) => railRoad(then(f)(r))
    })

Выделенный бит является последним railRoad битом. Мы можем использовать это следующим образом:

railRoad<User, *somePossibleErrors*>(getUser())
  .then(formatName)
  .map(greet)

Это имеет некоторые хорошие возможности, это позволяет нам обрабатывать все ошибки в конвейере функций произвольной длины, но в этом - мы должны указать множество ошибок мы должны справиться. Я бы хотел, чтобы e in railRoad был получен из функции map, а затем значение e функции.

Это то, что мы можем сделать? Так что, когда мы вызываем функции с .then или .map, их сигнатура добавляется к типу в исходной функции (railRoad)?

Пространственная площадка TS для этого кода здесь

1 Ответ

2 голосов
/ 03 февраля 2020

В дальнейшем я буду использовать соглашения об именах параметров типа TS (заглавные буквы), и я везде изменил «succes» на «success». Это изменения cosmeti c, которые вы можете игнорировать, если хотите. Я придерживался соглашения о ссылках на типы «success» как A и B, и я буду ссылаться на типы «fail» как E и F. Я признаю, что F не является хорошим названием для типа ошибки, но алфавит c, параллельный A / B, был слишком заманчивым для меня, чтобы сопротивляться.


Основная проблема возможно, ваша функция then() и функция railRoad() не имеют понятия расширения типа ошибки. Вот один из способов сделать это с помощью then():

const then = <A, B, F>(f: fun<A, Result<B, F>>) => <E>(r: Result<A, E>) =>
  join(map<A, Result<B, E | F>, E>(f)(r))

Здесь начальная функция берет что-то, что превращает A в Result<B, F>, а затем принимает Result<A, E> и производит Result<B, E | F>. Это объединение E и F в E | F является ключевым компонентом для того, чтобы ваши выводы работали позже.


Вторая проблема заключается в том, что ваша функция railRoad() использует поддержка логических типов c высшего порядка добавлена ​​в TypeScript 3.4, но конкретная формулировка приводит к довольно низкой производительности компилятора. Предполагаемый тип функции является рекурсивным типом типа {map: () => {map: () => ..... Чтобы предотвратить это, я создал интерфейс RailRoaded<A, E> для представления возвращаемого типа railRoad():

interface RailRoaded<A, E> {
  map<B>(f: (some: A) => B): RailRoaded<B, E>;
  then<B, F>(f: (some: A) => Result<B, F>): RailRoaded<B, E | F>;
}

const railRoad = <A, E>(r: Result<A, E>): RailRoaded<A, E> => ({
  map: <B>(f: (some: A) => B) => railRoad<B, E>(map<A, B, E>(f)(r)),
  then: <B, F>(f: (some: A) => Result<B, F>) => railRoad(then(f)(r))
})

Аннотирование возвращаемого типа railRoad как RailRoaded<A, E> значительно повышает производительность, поскольку компилятор имеет только проверить , что функция совместима и не пытается синтезировать для нее новый тип возвращаемого значения. В любом случае вы также можете увидеть, как метод then() для RailRoaded<A, E> генерирует тип ошибки объединенного типа.


Вот и все. Вот что происходит, когда вы называете это:

const chooChoo = railRoad(getUser()).then(formatName).map(greet);
// const chooChoo: RailRoaded<string, "no-user" | "no-name">

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

const engine = railRoad(getUser());
// const engine: RailRoaded<User, "no-user">
const car = engine.then(formatName);
// const car: RailRoaded<string, "no-user" | "no-name">
const caboose = car.map(greet);
// const caboose: RailRoaded<string, "no-user" | "no-name">

Также хорошо. Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...