Как стать автором
Обновить

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

Уж кому как ни reddit стоит рассказывать об успехах frontend разработки :)

Комментарий выглядит как намёк добавить тег irony )
Таки да, Reddit — визуальная каша нечитаемая. Лучше б «украли» концепцию у Хабра, тут ориентироваться на порядок проще и приятнее.
НЛО прилетело и опубликовало эту надпись здесь

В тему frontednt,


а в reddit действительно нет пред просмотра комментариев или я что-то упускаю?

НЛО прилетело и опубликовало эту надпись здесь
Огромное число раз видел ссылки типа «анонимус запостил нечто на реддит и развернулась активная дискуссия» — перехожу посмотреть, а там 10-20-30 комментариев. Ураган прямо! Один из самых популярных сайтов мира, однозначно. Бывают, конечно, действительно активные топики — но редко.
НЛО прилетело и опубликовало эту надпись здесь

Мобильная версия у них вроде ничего.


Но с десктопа на нее не попасть, редиректит на большую.

После прочтения возникает ощущение, что выбор делался примерно так:
— Flow лучше в этом и в этом, у TypeScript явных преимуществ как бы нет… Но все используют TypeScript, поэтому какого черта?! Ребята, будем использовать TypeScript!

Ну еще тулинг.

Просто типа стильно, модно, молодежно.

Смотрел, TypeScript — не понравилось. Пишем на ES2016. Команде нравится.

Что касамо типизации для входящих данных, то написал небольшой класс, который типизирует данные для последующей передачи в класс, валидатор или для сохранения. Хватает за глаза и незачем тянуть еще кучу всего. Что касамо чтения кода — то в команде договоренность — писать JSDoc для методов и классов.

И добавлю что перешли на async/await и код реально упростился и стал очень простым и читаемым.
Что конкретно команде нравится? Не писать типы и вместо них писать JSDoc? Или писать «небольшие» классы с валидаторами на каждый чих? Тянуть кучу всего — это о чём?
не понравилось

А что именно не понравилось? И чем в таком случае выигрывает ES2016?

Как будто хайп это что-то плохое — это означает комьюнити, библиотеки о чем автор в статьи таки сказал.

НЛО прилетело и опубликовало эту надпись здесь
Так как TypeScript — компилируемый язык, с его помощью можно создавать типы, которые определяются во время выполнения программы.

Что это за типы, определяемые во время выполнения? Насколько мне известно, типы выводятся на этапе транспиляции.
Так как TypeScript — компилируемый язык, с его помощью можно создавать типы, которые определяются во время выполнения программы.

TypeScript не компилириуемый, а транслируемый. Кроме того вся информацию о типах на этапе трансляции удаляется. Итоговый JavaScript ничего о типах не знает.

Одно другому не мешает: компиляция есть разновидность трансляции. Раньше считалось что компиляция — это такая трансляция когда на выходе — машинные коды, но появление JVM, CLR и LLVM значительно размыли понятие компилятора.


Кроме того вся информацию о типах на этапе трансляции удаляется. Итоговый JavaScript ничего о типах не знает.

Смотря о каких. Автор же приводит конкретный пример: перечисления. Они остаются в итоговом Javascript.

Знает, через рефлексию. Специальный флаг компилятора позволяет добавлять немного информации о типах в рантайм. Довольно куцую информацию правда.

И неожиданно — если в класс Bird добавить любое свойство, например flying: boolean, то Typescript вполне себе успешно ругается на push. Так что дело не в инвариантности, а в том, что не нужно плодить пустых наследников.

Утиная типизация же)


В typescript учебниках пишут об этом.

Все верно, просто структурная типизация не сразу в голове укладывается, вот люди и недоумевают.

К сожалению, смесь двух видов типизации до добра не доводит:


class A { foo: string }
class B { bar: string }

function test(x : A | B) {
  if (x instanceof A) {
    console.log(x.foo);
  } else {
    console.log(x.bar);
  }
}

test({ foo: "Hello, world" }); // undefined

Компилятор мог бы заметить ошибку при проверке типов — но не заметил.

Дело в том, что такие проверки через instanceof бессмысленны, так как проверяется не номинальное наследование, а наличие в цепочке прототипа конструктора. Так как A.prototype не находится в цепочке прототипов {foo: '123'} (что абсолютно верно), то выполняется else-ветка.
Далее из-за, опять-таки, структурной типизации интерфейс класса A идентичен интерфейсу { foo: string }, поэтому test в состоянии принять такой объект вместо инстанса.
Решением будет вместо обычного юниона использовать discriminated с общим ключом.


UPD: Номинальные типы пока на стадии обсуждения

Решение-то я знаю, но как заставить компилятор выругаться на некорректный код?

Можно вот так:


class A { 
    private __nominal_A: A; //или что еще покруче
    foo: string; 
}

Тогда в test ничего не запихнуть, кроме new A(), но как по мне лучше перестать пытаться писать на TS как на C#.

interface Object { __proto__ : this }

class A extends Object { foo: string }
class B extends Object { bar: string }

class C { foo: string }
class D extends Object { foo: string }
class E extends A {}

function test(x : A | B) {
  if (x instanceof A) {
    console.log(x.foo);
  } else {
    console.log(x.bar);
  }
}

test( new C ); // error
test( new D ); // ok
test( new E ); // ok
test({ foo: "Hello, world" }); // error

Не проверял, но если ваши примеры верны, то это ничуть не решение получается:


var d = new D();
d.foo = "Hello, world!";
test(d); // undefined
> К сожалению, смесь двух видов типизации до добра не доводит:

Никакой смеси нет. В тайпскрипте только структурный сабтайпинг (на данный момент). В вашем примере по факту, баг occurence typing'а — чекер не должен уметь выводить тип B для х в негативной ветке, только А в позитивной. Впрочем, это могло быть сделано и специально (правильное поведение вынудило бы переписывать ооочень много подобных кейзов).

Почему не должен? Type narrowing вполне себе работает. Если ввести в юнион третий тип, например


class A { foo: string }
class B { bar: string }
class C { foobar: string }

function test(x : A | B | C) {
  if (x instanceof A) {
    console.log(x.foo);
  } else {
    console.log(x.bar); //error
  }
}

то будет ошибка, так как bar не существует в оставшемся юнионе B | C

От того что ошибку замаскировали, лучше не стало. Потому что на самом деле в ветке else должно было остаться объединение A | B | C.

С какой стати? instanceof type guards
Это не ошибка, а ожидаемое поведение при структурной типизации.

С такой стати, что из провала проверки instanceof нельзя логически вывести структурное несоответствие типа (что и демонстрирует мой пример).


А тот факт, что подобное нелогичное поведение вдруг стало ожидаемым, и говорит о том, что "смесь двух видов типизации до добра не доводит".

> Почему не должен?

Потому что такая семантика у instanceof. Из x instanceof A следует, что в х есть поля А, но из того факта, что !(x instanceof A) не следует, что в х нет полей А, иными словами, у нас нету никаких корректных утверждений о типе значения в негативной ветке. Мы не можем утверждать, что в негативной ветке у нас тип !A, а значит, и не можем утверждать, что он A | B — A = B. По-этому narrowing должен быть только в позитивных ветках.
Собственно, может быть тип C < A | B, с ним код будет валиден в обеих ветках
Из x instanceof A следует, что в х есть поля А, но из того факта, что !(x instanceof A) не следует, что в х нет полей А

Из этого следует, что в цепочке прототипов x есть A.prototype, то есть у x есть поля, перечисленные именно в A. В негативную ветку тогда попадает утверждение, что в цепочке прототипов x нет A.prototype, соответственно в x нет и полей, перечисленных именно в A конкретно для этой ветки. Хотя, если юнион включает в себя A и что-то "еще", то при разнице останется это "еще", даже если оно включает в себя поля из A (повторяющиеся).


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

Посмотрите еще раз на мой пример. Прототипа — нет, а поля там только из A, полей из B — нет.

Ну так это же другая проблема. instanceof сам по себе не проверяет на структурное соответствие, он проверяет на наличие конструктора в прототипе. Соответствие выводится из того, что у вас юнион состоит из классов и вы используете instanceof, проверяющий как раз конструктор. Но я уже написал, что лучше instanceof вообще не использовать, так как снаружи вам всегда может прилететь объект такой же структуры, но не несущий в прототипе нужный конструктор.

Так о том и речь. Из положительной проверки instanceof следует структурное соответствие, а из отрицательной не следует ничего. В языке же почему-то из отрицательной проверки следует отсутствие структурного соответствия...

Эм. Если у меня юнион из 3х классов, значит объект с типом этого юниона содержит в цепочке какой-то из прототитипов этих конструкторов. Если он точно не содержит один из них (instanceof провалился), почему из этого не следует, что в негативной ветке он уже "содержит один из прототипов оставшихся конструкторов"?


Я правильно понимаю, что вы ведете к тому, что, будь в TS номинальная типизация, то можно было бы не беспокоиться внутри test о том, что снаружи прилетит объект с той же структурой но без нужного прототипа?

Я не "веду к этому", я об этом сказал в первом же сообщении.


Если мы разрешаем передачу { foo: "Hello, world!" } как значения типа A — то нельзя говорить о том, что в значении типа A | B есть хоть какие-то прототип и конструктор!

Но ведь это не совсем так… Мы разрешаем передачу объектов как значения интерфейса A, тогда как instanceof проверяет не интерфейс, а конструктор (то есть реальное js-значение — функцию).
И, понятно, что мы не можем использовать само значение для указания типа аргумента, только его интерфейс (который просто автоматически выводится).
Более того, если вы в юнион вместо классов объедините интерфейсы, вам даже instanceof выполнить не дадут.

Так в том-то и проблема, что из несоответствия конструктора не следует несоответствие интерфейса!

Правильно, но инстанс конструктора A имеет интерфейс A, но не всякий объект с интерфейсом A является инстансом конструктора A.
Тут проблема несколько иная. x instaceof A вам гарантирует наличие нужных полей, если в цепочке есть прототип A. Более того, можно instanceof использовать на любом object type для любого конструктора, даже не из юниона, тогда тип в ветке условия будет расширен прототипом дополнительного конструктора:


class A { foo: string }
class B { bar: string }
function test(x: B) {
  if (x instanceof A) {
    x.bar; //ok
    x.foo; //ok
  }
}

С другой стороны, интерфейс B (а в аргументах идет именно интерфейс, а не конструктор) не гарантирует наличия конструктора B в прототипе, а лишь гарантирует наличие полей из B где-то, либо на инстансе, либо в прототипе.


Из всего этого вытекает, что, даже если вы принимаете в аргументах объект с интерфейсом B, вы не можете гарантировать, что где-то в прототипе этого объекта не сидит конструктор A, и, соответственно, что нельзя применять instanceof A для объекта с интерфейсом B. Именно поэтому тайпчекер разрешает использовать классы не из юниона в instanceof.


Но если интерфейс A объединен в юнион с интерфейсом B, то в ветке x instanceof A вы уже рассмотрели интересующий вас случай, когда вам нужны поля из A. А если это произошло, то можно спокойно "вычитать" из юниона поля из A.

Но если интерфейс A объединен в юнион с интерфейсом B, то в ветке x instanceof A вы уже рассмотрели интересующий вас случай, когда вам нужны поля из A. А если это произошло, то можно спокойно "вычитать" из юниона поля из A.

Нет, в ветке x instanceof A был рассмотрен только случай совпадения прототипа. Но остался случай когда поля из A есть — а прототипа A нету.

Ну так если в аргументах B как выше, то в else и будет только B, так как ситуация, когда прототипа A нет, а поля есть, запрещена типом аргумента.


А если там A | B, то будет вычет, так как A | B подразумевает наличие полей. В этом месте нет проблемы, код корректный и сужение типа тоже корректное.


Проблема в том, что компилятор позволяет запихнуть в функцию структурно идентичный объект. И то, это не проблема, а особенность структурной типизации.


Пока что же, сужение в ветках с instanceof мне видится вполне валидным, так как тип аргумента функции test явно указывает на наличие полей.
Но я согласен, что это неудобно, неочевидно и нужно всегда про это помнить. Поэтому я просто не использую юнионы из классов и instanceof.

Ну так если в аргументах B как выше, то в else и будет только B, так как ситуация, когда прототипа A нет, а поля есть, запрещена типом аргумента.

В том-то и дело, что такая ситуация не запрещена.

Для тела функции запрещена. Для вызова нет.

> Из этого следует, что в цепочке прототипов x есть A.prototype, то есть у x есть поля, перечисленные именно в A.

В Тайпскрипте нет такого отношения. A < B => А содержит все поля B. Проверка типов для instanceof не может проверить что-либо кроме этого, потому что на уровне типов тайпскрипта ничто иное невыразимо. По-этому soundness чекер не должен выводить ничего для негативной ветки. Но в тайпскрипте намеренно жертвуют soundness ради удобства и более простой типизации существующего кода и привычных для жс паттернов, так что вполне возможно, что и тут оно — by design.

Ну как же нет, вон я выше показал пример, когда использование instanceof на дополнительном конструкторе расширяет тип в ветке интерфейсом этого конструктора.

Не интерфейсом, а _его полями_. В тайпскрипте нету номинальных типов. Нигде нету. И instanceof типизируется структурно (как и все остальное), по наличию полей, а не номинально. Утверждения вида «Х реализует интерфейс А» невозможно выразить на тайпскрипте. На уровне типизации нету никаких классов, интерфейсов и прототипов вообще. Есть только типы, которые либо содержат какие-либо поля, либо не содержат. Единственные утверждения, которые вы можете делать на тайпскрипте: «Х содержит поле Y».

Вы придираетесь к словам, понятно же, что типизация структурная.
Окей, для ветки if (x instanceof A), x содержит все поля интерфейса A. Так лучше?

> Вы придираетесь к словам, понятно же, что типизация структурная.

Это не придирка к словам, это то как работает алгоритм тайпчека (и от этих «придирок» он будет работать с разным результатом)

> Окей, для ветки if (x instanceof A), x содержит все поля интерфейса A. Так лучше?

Да, именно это утверждение формулируется на уровне типов. Но, вот проблема, сам instanceof в рантайме поля не проверяет. По-этому мы можем быть уверены в том, что в позитивной ветке поля есть. Но не можем быть уверены в том, что их нету в негативной.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий