Search
Write a publication
Pull to refresh

Comments 21

Мне про TypeScript понравился коммент одного разраба: "TypeScript делает простые вещи сложными, а сложные вещи простыми". И в итоге Макрософт пытались с помощью TypeScript обуздать "чудовище" (JS), которое они однажды создали, но как-то не туда все пошло опять

Да ладно вам, будто в плюсовом мире меньше идиотизма.

А когда это Майкрософт создал JS?

TypeScript делает простые вещи сложными, а сложные – any.

мне это нравится даже больше))

С пунктом 4 есть ещё более интересный прикол. Да, в object напрямую не засунуть примитив, но можно так:

const nn: {} = 1;
const obj: object = nn;

То есть object оказался надтипом для более широкого {}, что идет вразрез с иерархией типов.

П.3 широко известен, во многих библиотеках есть его исправление, добавляющее перегрузку для метода filter.

П.5 - следствие ковариантности изменяемого массива, добавленной для удобства и в своё время вызвавшей много споров.

П.3 Да, например ts-reset знаю

П.4 не знала)

По моему, стоит задиприкейтить {} потому что сейчас это пережиток прошлого.

В этом примере мы ожидаем, что тип arr будет number[], но на самом деле получаем (number | undefined)[], потому что TypeScript не может точнее определить тип возвращаемого значения. Это неудобно: ты точно знаешь, что в рантайме undefined не будет, но в типах обязан это учитывать

Нет. Все обстоит не так. Начиная с версии 5.5 Typescript корректно выводит предикаты типов. Но должны соблюдаться два условия:

1. Типа предикат должен возвращать тип Т, когда функция возвращает true

2. Когда функция возвращает false входной параметр точно не Т

А что мы видимо в предложенном примере? Когда у нас значение 0, то Boolean возвращает false. Поэтому Boolean не является корректным предикатом типов для number. Поэтому typescript и не меняет number | undefined на number. И правильно делает, потому что тогда бы разработчики допускали ошибку в коде.

Спасибо, обязательно уточню этот момент

Если более обще посмотреть. То имеем:

interface BooleanConstructor {
    new (value?: any): Boolean;
    <T>(value?: T): boolean;
    readonly prototype: Boolean;
}

Иными словами тут всегда выход - это boolean, а для предиката типов должна быть ясная связь между типом входного значения и типом выходного. Поэтому при использовании Boolean и возможны ошибки типа той, что в примере из статьи.

Когда у нас значение 0, то Boolean возвращает false. Поэтому Boolean не является корректным предикатом типов для number

Почему? Boolean отсеивает значения undefined и null, и вполне мог бы быть, например, тайпгардом (x: number | undefined) => x is number. Но он протипизирован не как тайпгард, а как обычный предикат, потому и не выкидывает undefined и null, как при вызове напрямую, так и через filter.

В версии 5.5 просто добавили несколько эвристик, по которым в ряде случаев не заданное явно возвращаемое значение функции будет выведено как предикат типа, а не просто логическое, например const isNum = (x: number | undefined) => x != null;. К случаю с Boolean это не относится

Так я же написал почему. Мы имеем число 0 для которого Boolean(0) возвращает false. Иными словами Boolean только что отсеял значение с типом number. Т.е. для number Boolean не является корректным предикатом типа.

Но вообще, я выше описал общий случай почему Boolean в принципе не является предикатом типа.

Т.е. для number Boolean не является корректным предикатом типа.

Так он проверяет не на число, а на "nonfalsy". То есть правильная тайпгардная типизация внутри BooleanConstructor была бы примерно такая:

<T>(value?: T): value is Exclude<T, null | undefined | 0 | 0n | '' | false>;

Любопытно, что если добавить эту сигнатуру в BooleanConstructor через слияние интерфейсов, то напрямую тайпгард начинает работать (if (Boolean(v)) { ... v ... }), а в filter не подхватывается

Хороший дядька. Переживал за его отношения с Растом сильнее чем за свои.

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

Да, спорных вещей в Typescript хватает. Некоторую функциональность, имхо, не стоило вообще добавлять, например, declaration merging.
```

interface A {
    getFromInterface(): string;
}

class A {
    getFromClass(): string {
        return ''
    }
}

new A().getFromInterface();

ошибки компиляции не будет)) Рантайм, конечно, все очевидно)

По первому примеру. Чтобы не возникало подобных коллизий, можно внедрить номинальную типизацию вместо структурной, это хорошо помогает в сложных системах. Например, так
```

declare const BrandA: unique symbol;

class A {
    [BrandA]!: void;

    name = 'John';
}

function getName(obj: A): string {
    return obj.name;
}

// no error
getName(new A()); 

/** @throws compile error: 
 * <b>Argument of type '{ name: string; }' is not assignable to parameter of type 'A'.
 *   Property '[BrandA]' is missing in type '{ name: string; }' but required in type 'A'</b>
 */
getName({name: 'name'});

Sign up to leave a comment.

Articles