Арифметические ограничения для хэш-элементов машинописного текста

#typescript

Вопрос:

Могу ли я каким-то образом определить тип в машинописном тексте, который также применяет аритмические ограничения к реквизитам объекта, например.

 type RestrictedObject = {
a: number,
b: number,
}
 

так что a % b = 0

Комментарии:

1. Ты не можешь. Единственное, что вы можете сделать, это использовать геттеры и сеттеры и выполнить проверку при установке нового значения

Ответ №1:

Нет, это потребовало бы проверки во время выполнения, что выходит за рамки типоскрипта.

Вы можете определить объект со свойствами метода доступа, чтобы принудительно применить отношение:

 class Restricted {
    #a: number;
    #b: number;

    static #valid(a: number, b: number): boolean {
        return a % b === 0;
    }

    constructor(a: number,  b:number) {
        if (Restricted.#valid(a, b)) {
            throw new Error(`The values of 'a' and 'b' must be such that 'a % b' is 0; ${a} and ${b} don't fit`);
        }
        this.#a = a;
        this.#b = b;
    }

    get a() {
        return this.#a;
    }
    set a(value) {
        if (this.#b !== null) {
            if (Restricted.#valid(value, this.#b)) {
                throw new Error(`The value of 'a' cannot be ${value} when 'b' is ${this.#b}`);
            }
        }
        this.#a = value;
    }

    get b() {
        return this.#b;
    }
    set b(value) {
        if (this.#a !== null) {
            if (Restricted.#valid(this.#a, value)) {
                throw new Error(`The value of 'b' cannot be ${value} when 'a' is ${this.#a}`);
            }
        }
        this.#b = value;
    }
}
 

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

Ответ №2:

Если вы хотите писать меньше кода, вы можете просто выдать ошибку в конструкторе:

 class RestrictedObject {
  constructor(public a: number, public b: number) {
    if (a % b !== 0) {
      throw new Error(`Remainder of ${a}/${b} must be zero.`);
    }
  }
}

console.log(new RestrictedObject(4, 3).a);
// Error: Remainder of 4/3 must be zero.
 

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

 type Length<T extends any[]> = T extends { length: infer L } ? L : never;

type BuildTuple<L extends number, T extends any[] = []> = T extends { length: L }
  ? T
  : BuildTuple<L, [...T, any]>;

type Subtract<A extends number, B extends number> = BuildTuple<A> extends [...infer U, ...BuildTuple<B>]
  ? Length<U>
  : never;

type EQ<A, B> = A extends B ? (B extends A ? true : false) : false;
type AtTerminus<A extends number, B extends number> = A extends 0 ? true : B extends 0 ? true : false;

type LT<A extends number, B extends number> = AtTerminus<A, B> extends true
  ? EQ<A, B> extends true
    ? false
    : A extends 0
    ? true
    : false
  : LT<Subtract<A, 1>, Subtract<B, 1>>;

type Modulo<A extends number, B extends number> = LT<A, B> extends true ? A : Modulo<Subtract<A, B>, B>;

type RestrictedObject<A extends number, B extends number, T = Modulo<A, B>> = {
  a: T extends 0 ? A : never;
  b: T extends 0 ? B : never;
};

const foo: RestrictedObject<4, 2> = {
  a: 4,
  b: 2
};
 

И когда ошибаешься:

 const foo: RestrictedObject<4, 3> = {
  a: 4, // type error: Type 'number' is not assignable to type 'never'.
  b: 2 // type error: Type 'number' is not assignable to type 'never'.
};