Можем ли мы смоделировать «аргументы по умолчанию» для общих c параметров? - PullRequest
0 голосов
/ 06 августа 2020

Я построил небольшую библиотеку на уровне типов, чтобы упростить написание пар функций вида

function a(...): T | undefined;
function b(...): T;

, где b получается из a путем выдачи исключения, если возвращается undefined. Основная идея c заключается в следующем:

export enum FailMode { CanFail, CanNotFail }
export const canFail = FailMode.CanFail;
export const canNotFail = FailMode.CanNotFail

export type Failure<F extends FailMode> = F extends FailMode.CanFail ? undefined : never;
export type Maybe<T, F extends FailMode> = T | Failure<F>;

export function failure<F extends FailMode>(mode: F): Failure<F> {
  if (mode === canFail) return undefined as Failure<F>;
  throw new Error("failure");
}

Теперь мы можем объединить a и b в одну функцию, которая принимает один дополнительный параметр для различения guish типов:

function upperCaseIfYouCan<F extends FailMode>(x: string | undefined, mode: F): Maybe<string, F> {
  if (x === undefined)
    return failure<F>(mode);
  return x.toUpperCase();
}

// both of these now work
let y: string = upperCaseIfYouCan("foo", canNotFail);          // can throw, but will never return undefined
let z: string | undefined = upperCaseIfYouCan("foo", canFail); // cannot throw, but can return undefined

Теперь в большинстве случаев мне нужен вариант canNotFail, и мне интересно, есть ли способ сделать его «по умолчанию», чтобы мне не нужно было передавать canNotFail параметр в этом случае, чтобы работало следующее:

let y: string = upperCaseIfYouCan("foo");  // can throw

Я уже определил, что этого нельзя достичь с помощью аргументов по умолчанию, потому что тип аргумента по умолчанию должен быть унифицированным с F. Есть ли способ добиться этого другим способом, чтобы определение таких функций, как upperCaseIfYouCan, было таким же простым?

1 Ответ

1 голос
/ 06 августа 2020

Компилятор недоволен тем, что вы указали параметр по умолчанию, потому что кто-то всегда может прийти и вручную указать параметр типа generi c при вызове, например: upperCaseIfYouCan<FailMode.CanFail>("");. Если вы не ожидаете, что это произойдет, вы можете использовать утверждение типа , чтобы подавить эту ошибку, и, кроме того, вы должны предоставить параметру типа F свой собственный default , чтобы без очевидный выбор для F, компилятор выберет ваше значение по умолчанию вместо полного FailMode:

function upperCaseIfYouCan<F extends FailMode = FailMode.CanNotFail>(
    x: string | undefined,
    mode: F = canNotFail as F
): Maybe<string, F> {
    if (x === undefined)
        return failure<F>(mode);
    return x.toUpperCase();
}

Это должно работать с вашими вариантами использования:

let str = upperCaseIfYouCan("foo", canNotFail); // string
let strOrUndef = upperCaseIfYouCan("foo", canFail); // string | undefined

// this is the behavior you want
str = upperCaseIfYouCan("foo"); // string

С другой стороны, вы можете просто использовать overloads для обработки двух разных способов вызова вашей функции. Хотя обобщения концептуально более элегантны, в этой ситуации перегрузки могут быть более интуитивными: называется неправильно (нет F, чтобы указать вручную).

Хорошо, надеюсь, что один из них подскажет вам направление. Удачи!

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...