Typescript: Предложение: тип, не допускающий значения NULL

Созданный на 22 июл. 2014  ·  358Комментарии  ·  Источник: microsoft/TypeScript

Представьте два новых синтаксиса для объявления типа на основе JSDoc

var myString: !string = 'hello world'; //non-nullable
var myString1: ?string = 'hello world'; // nullable 
var myString2: string = 'hello world'; // nullable 
var myString3 = 'hello world'; // nullable

по умолчанию тип допускает значение NULL.

Два новых флага компилятора:

  • inferNonNullableType заставляет компилятор вывести тип, не допускающий значения NULL:
var myString3 = 'hello world' // typeof myString is '!string', non-nullable
  • nonNullableTypeByDefault (думаю, может быть имя получше):
var myString: !string = 'hello world'; // non-nullable
var myString1: string = 'hello world'; // non-nullable 
var myString2: ?string = 'hello world'; // nullable 
var myString3 = 'hello world' // non-nullable
Committed Fixed Suggestion

Самый полезный комментарий

Извините, если я прокомментирую закрытый вопрос, но я не знаю, где лучше спросить, и я не думаю, что стоит выпускать новый выпуск, если нет интереса.
Можно ли обрабатывать неявное значение null для каждого файла?
Например, обрабатывать кучу файлов td с помощью noImplicitNull (потому что они исходят из определенно типизированного и задумывались таким образом), но обрабатывать мой источник как implicitNull?
Кто-нибудь найдет это полезным?

Все 358 Комментарий

Я предлагаю использовать в качестве примера тип, отличный от строкового, поскольку он по своей природе допускает значение NULL. :П
Я могу воспринимать типы, не допускающие значения NULL, как проблемные, поскольку пользователь и компилятор "!" ожидает, что тип всегда будет отличным от нуля, что никогда не может быть истинно утверждено в JavaScript. Пользователь может определить что-то вроде этого:

function(myNonNull:!myClass):void {
  myNonNull.foo();
}

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

Теперь, с учетом сказанного, возможно, решение могло бы заключаться в том, что для общедоступных методов он мог бы автоматически утверждать не null. Но тогда компилятор также может утверждать, что у вас не может быть общедоступных свойств (или частных, если на то пошло), которые могут иметь объявление! Nonnull, поскольку они не могут быть применены.

Это может привести к более глубокому обсуждению контрактов кода, чтобы это было должным образом реализовано.

Простите моих критиков, я думаю, что очень мало нужды в типах, не допускающих значения NULL, если / как только здесь появятся алгебраические типы данных. Причина, по которой люди используют null для представления отсутствующего значения, заключается в том, что нет лучшего способа сделать это в JavaScript и в большинстве языков ООП. Итак, ADT для визуализации уже здесь. Тогда, что касается старых библиотек, написанных до значений, не допускающих значения NULL, их наличие не улучшит жизнь. Что касается новых библиотек, то с помощью ADT можно очень точно смоделировать, какое значение может принимать значение в соответствии со спецификацией бизнес-области, вообще не используя нули. Итак, я предполагаю, что я говорю, что ADT - гораздо более мощный инструмент для решения той же проблемы.

Лично я просто написал небольшой интерфейс Maybe<T> и использую дисциплину, чтобы гарантировать, что переменные этого типа никогда не будут нулевыми.

Я предлагаю использовать в качестве примера тип, отличный от строкового, поскольку он по своей природе допускает значение NULL. :П
Я могу воспринимать типы, не допускающие значения NULL, как проблемные, поскольку пользователь и компилятор "!" ожидает, что тип всегда будет отличным от нуля, что никогда не может быть истинно утверждено в JavaScript. Пользователь может определить что-то вроде этого:

function (myNonNull:! myClass): void {
myNonNull.foo ();
}
И поскольку он определен как ненулевой, все может быть хорошо для компилятора, но затем кто-то другой, кто использует его в javascript, передает что-то null и kaboom.

Я действительно не понимаю, что вы также можете определить:

function myFunc(str: string): int {
 return str && str.length;
}

и если кто-то передаст этой функции int , это также закончится с ошибкой, преимущество машинописного текста заключается в том, чтобы делегировать компилятору передать то, что вы бы проверили вручную в javascript, имея еще одну проверку на наличие / отсутствие значения NULL. Тип -nullable мне кажется разумным. Кстати, SaferTypeScript и ClosureCompiler уже проводят такую ​​проверку.

С типами объединения у нас могла бы быть довольно простая спецификация для этого.
Допустим, у нас теперь есть базовый тип null, у нас может быть более строгий режим, в котором null и undefined несовместимы ни с одним типом, поэтому, если мы хотим выразить значение, допускающее значение NULL, мы бы сделали:

var myNullableString: (null | string);
var myString = "hello";
myNullableString = myString //valid
myString = myNullableString // error null is not assignable to string;

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

var myString: string; // error
var myNullableString: (null | string); // no error

function myString(param1: string, param2?: string) {
  // param1 is string
  // param2 is (null | string)
}

@fdecampredon +1

IIRC из того, что Facebook показал Flow, который использует синтаксис TypeScript, но с типами, не допускающими значения NULL, по умолчанию они поддерживают сокращение для (null | T) как в вашем исходном сообщении - я думаю, что это было ?T или T? .

var myString: string; // error

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

var myString: string;
if (x) {
myString = a;
} else if (y) {
myString = b;
} else {
myString = c;
}

В Rust, например, это нормально, если компилятор видит, что myString будет инициализирован до того, как он будет использован, но вывод TypeScript в настоящий момент не поддерживает этого.

Честно говоря, делать что-то вроде var myString = '' вместо var myString: string меня не особо беспокоит, но я уверен, что такое правило возможно всегда.

@fdecampredon +1 за это - идея мне очень нравится. Для баз кода, которые на 100% состоят из JavaScript, это было бы полезным ограничением только во время компиляции. (Насколько я понимаю ваше предложение, сгенерированный код не имеет намерения обеспечить это?)

Что касается сокращения для (null | string) sure ?string в порядке.
И конечно, @johnnyreilly, это всего лишь проверка времени компиляции

Типы суммы делают типы, не допускающие значения NULL, по умолчанию очень интересны. Свойства безопасности не допускающего значения NULL по умолчанию невозможно переоценить. Суммарные типы плюс запланированная деструктуризация if / typeof (не знаю, как это следует называть) даже делают его типобезопасным для интеграции API-интерфейсов, допускающих и не допускающих значения NULL.

Однако создание типов, не допускающих значения NULL по умолчанию, является огромным критическим изменением, которое потребует изменения почти всех существующих сторонних файлов определения типов. Хотя я на 100% поддерживаю критическое изменение, никто не может обновить определения типов, которые существуют в дикой природе.

Хорошо, что большая часть этих определений собрана в репозитории DefininiteTyped, но у меня все еще есть практические опасения по поводу этой функции.

@samwgoldman идея состоит в том, чтобы иметь типы, не допускающие значения NULL, только под специальным флагом компилятора, например nonImplicitAny этот флаг может называться strict или nonNullableType . Так что критических изменений не будет.

@fdecampredon А как насчет определений типов для библиотек, не относящихся к TypeScript, например, в DefinentyTyped? Эти определения не проверяются компилятором, поэтому любой сторонний код, который может возвращать null, необходимо будет повторно аннотировать для правильной работы.

Я могу представить определение типа для функции, которая в настоящее время помечена как «возвращает строку», но иногда возвращает null. Если бы я зависел от этой функции в моем коде nonNullableType , компилятор не жалуется (как он мог?), И мой код больше не является нулевым.

Если я чего-то не упускаю, я не думаю, что эту функцию можно включать и выключать с помощью флага. Мне кажется, что это семантическое изменение по принципу «все или ничего» для обеспечения совместимости. Я был бы счастлив, если бы ошибся, потому что думаю, что функция переключения флагов более вероятна.

Кстати, о компиляторе Facebook Flow пока доступно не так много информации, но из видеозаписи презентации кажется, что они по умолчанию не допускают значения NULL. Если так, по крайней мере, здесь есть приоритет.

Хорошо, допустим, есть сокращение ? type для type | null | undefined .

@fdecampredon А как насчет определений типов для библиотек, не относящихся к TypeScript, например, в DefinentyTyped? Эти определения не проверяются компилятором, поэтому любой сторонний код, который может возвращать null, необходимо будет повторно аннотировать для правильной работы.

Я могу представить определение типа для функции, которая в настоящее время помечена как «возвращает строку», но иногда возвращает null. Если бы я зависел от этой функции в моем коде nonNullableType'ed, компилятор не жалуется (как он мог?), И мой код больше не является нулевым.

Я не вижу проблемы, уверен, что некоторые файлы определений не будут действительны в режиме nonNullableType , но в большинстве случаев хорошая библиотека избегает возврата null или undefined так что определение будет верным в большинстве случаев.
В любом случае, я лично редко могу выбрать определение с определенным типом без необходимости проверять / изменять его, вам просто нужно немного поработать, чтобы добавить префикс ? с некоторыми определениями.

Если я чего-то не упускаю, я не думаю, что эту функцию можно включать и выключать с помощью флага. Мне кажется, что это семантическое изменение по принципу «все или ничего» для обеспечения совместимости. Я был бы счастлив, если бы ошибся, потому что думаю, что функция переключения флагов более вероятна.

Я не понимаю, почему у нас не может быть функции переключения флагов, правила были бы простыми:

  • в обычном режиме ? string эквивалентно string а null или undefined присваиваются всем типам
  • в режиме nonNullableType ? string эквивалентно string | null | undefined а null или undefined не могут быть присвоены никакому другому типу, кроме null или undefined

Где несовместимость с функцией переключения флагов?

Флаги, изменяющие семантику языка, опасны. Одна из проблем заключается в том, что эффекты потенциально очень нелокальные:

function fn(x: string): number;
function fn(x: number|null): string;

function foo() {
    return fn(null);
}

var x = foo(); // x: number or x: string?

Важно, чтобы кто-то, просматривающий фрагмент кода, мог «следить» за системой типов и понимать сделанные выводы. Если у нас появится куча флагов, изменяющих правила языка, это станет невозможным.

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

Я знаю, что это нарушит ретро-совместимость, и я понимаю точку зрения @RyanCavanaugh , но после того, как попробовал это с flowtype, это действительно бесценная функция, я надеюсь, что она станет частью машинописного текста.

В дополнение к комментарию Райана Кавано -> Из того, что я где-то читал, в спецификации / предложении ES7 упоминается использование перегрузки функций (то же имя функции, но другой тип данных входного параметра). Это очень необходимая функция для Javascript.

Из потоковой документации :

Flow считает, что null - это отдельное значение, не принадлежащее ни к какому другому типу.

var o = null;
print(o.x); // Error: Property cannot be accessed on possibly null value

Любой тип T может быть включен для включения null (и связанного с ним значения undefined), написав? T

var o: ?string = null;
print(o.length); // Error: Property cannot be accessed on possibly null or undefined value

[Flow] понимает влияние некоторых тестов динамического типа.

(т.е. на жаргоне TS понимает охранников типа)

var o: ?string = null;
if (o == null) {
  o = 'hello';
}
print(o.length); // Okay, because of the null check

Ограничения

  • Проверки свойств объекта ограничены из-за возможности алиасинга:

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

  • Проверки в стиле защиты типа могут быть избыточными для свойств объекта.

[D] не следует ожидать, что поле, допускающее значение NULL, будет распознано как ненулевое значение в каком-либо методе, потому что проверка NULL выполняется каким-либо другим методом в вашем коде, даже если вам ясно, что проверка NULL достаточна для безопасности в время выполнения (скажем, потому что вы знаете, что вызовы первого метода всегда следуют за вызовами второго метода).

  • undefined не проверяется.

Неопределенные значения, как и null, тоже могут вызывать проблемы. К сожалению, неопределенные значения повсеместно встречаются в JavaScript, и их трудно избежать, не сильно влияя на удобство использования языка. Например, в массивах могут быть отверстия для элементов; свойства объекта можно динамически добавлять и удалять. В этом случае Flow идет на компромисс: он обнаруживает неопределенные локальные переменные и возвращаемые значения, но игнорирует возможность неопределенности в результате доступа к свойствам объекта и элементам массива.

Что, если опция будет добавлена ​​одновременно с введением типа null (и сокращенного знака вопроса)? Присутствие в файле типа null заставит компилятор перейти в режим, не допускающий обнуления, для этого файла, даже если флаг отсутствует в командной строке. Или это слишком волшебно?

@jbondc кажется хорошим. однако проблема в том, что в итоге везде будет ! : p

It's tempting to want to change JavaScript but the reality is a 'string' is nullable or can be undefined.

Что это значит? В js нет статических типов. Итак, да, строки допускают значение NULL, но давайте не будем забывать, что они также могут быть нумерованными, объектными, заданными и т. Д. Любое значение может иметь любой тип.

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

Такие директивы, как «use strict», которые вызывают изменения семантики в области видимости, уже являются частью языка; Я думаю, что было бы разумно иметь в TypeScript директиву «использовать типы, не допускающие значения NULL».

@metaweta Я не думаю, что этого достаточно, например, что произойдет, если _non null module_ потребляет модуль, допускающий значение NULL:

//module A
export function getData(): string[] {
  return null;
}
//module B
'use nonnull'
import A = require('./A');

var data: string[] = A.getData();

data в модуле B фактически допускает значение NULL, но поскольку 'use nonnull' не использовался в модуле A, должны ли мы сообщать об ошибке?
Я не вижу способа решить эту проблему с помощью функции на основе директив.

Да,

var data: string[] = A.getData();

вызовет ошибку. Вместо этого вам нужно будет указать значение по умолчанию, когда getData() возвращает null :

var data: string[] = A.getData() || [];

@metaweta хорошо, но как узнать, что это ошибка? :)
тип getData по-прежнему '() => string []'. Вы бы автоматически обрабатывали все, что поступает из «модуля, допускающего значение NULL», как «допускающее значение NULL»?

Да, именно так (если тип из модуля, допускающего значение NULL, явно не отмечен иначе).

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

Лично я думаю, что уже немного поздно вносить это изменение, и @RyanCavanaugh прав, это изменение сделает Typescript менее предсказуемым, поскольку вы не сможете определить, что происходит, просто взглянув на файл.

Начинаются ли проекты с включенным или выключенным по умолчанию этим флагом компилятора? Если кто-то работает над проектом, не допускающим значения NULL по умолчанию, и создаст / переключится на проект по умолчанию, допускающий значение NULL, это вызовет путаницу?
В настоящее время я работаю с No Implicit Any в большинстве своих проектов, и всякий раз, когда я сталкиваюсь с проектом, в котором эта опция не включена, это меня застает врасплох.

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

@RyanCavanaugh беспокоился о нелокальности, а директивы имеют лексическую область видимости. Вы не сможете получить больше местных, если не аннотируете каждый сайт. Я не особо поддерживаю эту директиву; Я просто указывал на то, что такая возможность существует и что она, по крайней мере, так же разумна, как «использовать строгий» в ES5. Я лично поддерживаю типы, не допускающие значения NULL по умолчанию, но на практике для этого уже слишком поздно. Учитывая эти ограничения, я предпочитаю использовать! как-то. Предложение @jbondc позволяет отличать null от undefined; учитывая, что серверная часть Java продолжает заставлять людей использовать оба значения, мне он кажется наиболее полезным.

Прошу прощения, если я не понял, я был согласен с Райаном и добавил свои собственные опасения.

Честно говоря, если добавление use not-null - это цена за избежание всего исключения нулевого указателя, я бы заплатил его без каких-либо проблем, учитывая, что null или undefined как присваиваемые любому типу - худшая ошибка этот машинописный текст сделан на мой взгляд.

@jbondc Я не использовал "строгое использование" и поэтому делаю некоторые предположения. Пожалуйста, поправьте меня, если мои предположения неверны:

Не null не влияет на синтаксис, который пишет программист, но влияет на возможности следующего программиста, который пытается использовать этот код (при условии, что создатель и пользователь - разные люди).

Итак, код:

function myfoo (mynumber: number) {
    return !!mynumber;
} 

(печатая на телефоне, может ошибаться)
Допустимый код как в обычном проекте, так и в ненулевом проекте. Единственный способ узнать, работает ли код кодировщик, - это посмотреть на аргументы командной строки.

На работе у нас есть тестовый проект (который включает в себя прототипирование новых функций) и основной проект (с нашим реальным кодом). Когда прототипы готовы к перемещению из одного проекта в другой (обычно с большими рефракторами), в коде не будет ошибок, но будут ошибки в использовании кода. Это поведение отличается от no implicit any и использует strict, что сразу приведет к ошибке.

Теперь у меня есть достаточное влияние на эти проекты, поэтому я могу предупредить ответственных лиц, чтобы они не использовали эту новую `` функцию '', потому что это не сэкономит время, но у меня нет такой возможности для всех проектов на Работа.
Если мы хотим включить эту функцию хотя бы в одном проекте, то мы должны включить ее во всех других наших проектах, потому что у нас есть очень значительный объем совместного использования кода и миграции кода между проектами, и эта «функция» может вызвать у нас много возврата времени и «исправления» уже выполненных функций.

Право @jbondc. @Griffork : Извините, я не уловил этого недоразумения; Директива - это буквальное строковое выражение, которое появляется как первая строка производства программы или функции, и его влияние ограничено этим продуктом.

"use not-null";
// All types in this program production (essentially a single file) are not null

против

function f(n: number) {
  "use not-null";
  // n is not null and local variables are not null
  function g(s: string) {
    // s is not null because g is defined in the scope of f
    return s.length;
  }
  return n.toFixed(2);
}

function h(n: number) {
  // n may be null
  if (n) { return n.toFixed(3); }
  else { return null; }
}

Типы, не допускающие значения NULL, бесполезны. Типы, не допускающие значения NULL, являются полезными значениями. Они бесполезны. Бесполезный! Вы этого не понимаете, но они вам и не нужны. Нет смысла ограничивать себя, заявляя, что с этого момента мы не будем использовать NULL. Как бы вы изобразили пропущенное значение, например, в ситуации, когда вы пытаетесь найти подстроку, которой нет? Невозможность выразить отсутствующее значение (что сейчас делает NULL) не решит вашу проблему. Вы променяете суровый мир с NULL повсюду на столь же суровый, без каких-либо пропущенных значений. То, что вам действительно нужно, называется алгебраическими типами данных, которые (среди многих других интересных вещей) обладают способностью представлять отсутствующее значение (то, что вы ищете в первую очередь, и то, что представлено NULL в императивном мире). Я категорически против добавления в язык значений, не допускающих значения NULL, потому что это выглядит как бесполезный синтаксический / семантический мусор, который является наивным и неудобным решением известной проблемы. Прочтите о Optionals в F # и Maybe в Haskell, а также о вариантах (также известных как помеченные союзы, размеченные союзы) и сопоставлении с образцом.

@ aleksey-bykov Похоже, вы не знаете, что в JavaScript есть два нулевых значения: undefined и null . Значение null в JavaScript возвращается только в несоответствующем регулярном выражении и при сериализации даты в JSON. Единственная причина, по которой он вообще используется в языке, - это взаимодействие с апплетами Java. Переменные, которые были объявлены, но не инициализированы, - это undefined , а не null . Отсутствующие свойства объекта возвращают undefined , а не null . Если вы явно хотите, чтобы undefined было допустимым значением, вы можете протестировать propName in obj . Если вы хотите проверить, существует ли свойство в самом объекте, а не унаследовано ли оно, используйте obj.hasOwnProperty(propName) . Отсутствующие подстроки возвращают -1: 'abc'.indexOf('d') === -1 .

В Haskell свойство Maybe полезно именно потому, что не существует универсального подтипа. Нижний тип Haskell представляет собой не завершение, а не универсальный подтип. Я согласен с тем, что необходимы алгебраические типы данных, но если мне нужно дерево, помеченное целыми числами, я хочу, чтобы каждый узел имел целое число, а не null или undefined . Если мне это нужно, я использую дерево с меткой Maybe int или застежку-молнию.

Если мы примем директиву «use not-null», я бы также хотел «use not-void» (ни null, ни undefined).

Если вы хотите гарантировать, что ваш собственный код не содержит нулей, просто запретите нуль
литералы. Это намного проще, чем разрабатывать типы, не допускающие значения NULL. Неопределенный - это
немного сложнее, но если вы знаете, откуда они берутся, тогда
вы знаете, как их избежать. Низ в Haskell бесценен! Если бы
В JavaScript (TypeScript) был глобальный супертип без значения. Я скучаю по этому
плохо, когда мне нужно добавить выражение. Я использую TypeScript
начиная с версии 0.8 и никогда не использовал нули, не говоря уже о том, чтобы они были необходимы. Просто не обращай внимания
их, как и с любой другой бесполезной языковой функцией, например with
утверждение.

@ aleksey-bykov Если я пишу библиотеку и хочу гарантировать, что входные данные не равны нулю, я должен везде проводить для нее тесты во время выполнения. Мне нужны тесты во время компиляции, их нетрудно предоставить, и как Closure, так и Flow обеспечивают поддержку типов, отличных от null / undefined.

@metaweta , вы не можете гарантировать себе отсутствие нулей. Прежде чем ваш код будет скомпилирован, существует множество способов заставить вашу библиотеку плакать: pleaseNonNullablesNumbersOnly(<any> null) . После компиляции в js правил вообще нет. Во-вторых, какое вам дело? Скажите это громко и четко заранее nulls are not supported, you put a null you will get a crash , как отказ от ответственности, вы не можете гарантировать себя от всех типов людей, но вы можете очертить круг своих обязанностей. В-третьих, я с трудом могу представить себе крупную стандартную библиотеку, которая была бы пуленепробиваемой для любого пользователя, который мог бы вводить ее в качестве входных данных, но все же она по-прежнему безумно популярна. Так стоит ли ваших усилий хлопот?

@ aleksey-bykov Если клиенты моей библиотеки также проверены на тип, то я, конечно, могу гарантировать, что не получу нуль. В этом весь смысл TypeScript. По вашим рассуждениям, в типах вообще нет необходимости: просто «скажите громко и ясно» в вашей документации, какой тип ожидается.

Не по теме, нули чрезвычайно ценны для нас, потому что их проверка выполняется быстрее, чем проверка на undefined.
Хотя мы не используем их везде, мы стараемся использовать их там, где это возможно, для представления неинициализированных значений и пропущенных чисел.

По теме:
У нас никогда не было проблем с «уходом» нулей в другой код, но у нас были проблемы с появлением случайных undefinedes или NaN. Я считаю, что в этом сценарии тщательное управление кодом лучше, чем флаг.

Однако для библиотечных типизаций было бы неплохо иметь избыточный тип null, чтобы мы могли аннотировать функции, которые могут возвращать null (это должно выполняться не компилятором, а методами кодирования).

@metaweta , по моим рассуждениям, ваши клиенты не должны использовать нули в своей базе кода, это не так уж сложно, выполните полный поиск нуля (с учетом регистра, целое слово) и удалите их все. Слишком неуклюжий? Добавьте переключатель компилятора --noNullLiteral для изящности. Все остальное остается неизменным, тот же код, никаких проблем, гораздо более легкое решение с минимальным размером. Вернемся к моей точке зрения, предположим, что ваши типы, не допускающие значения NULL, нашли свой путь в TS и доступны в двух разных вариантах:

  • можно использовать синтаксис ! для обозначения типа, который не может принимать значение NULL, например string! не может принимать значение NULL
  • Переключатель noNullsAllowed включен

то вы получаете кусок json со своего сервера через ajax с нулями повсюду, мораль: динамический характер javascript не может быть исправлен с помощью аннотации типа поверх него

@ aleksey-bykov Точно так же, если я ожидаю объект с числовым свойством x и получаю от сервера {"x":"foo"} , система типов не сможет это предотвратить. . Это обязательно ошибка времени выполнения и неизбежная проблема при использовании на сервере чего-то другого, кроме TypeScript.

Если, однако, сервер написан на TypeScript и работает на узле, то его можно транспилировать при наличии файла .d.ts для моего внешнего кода, и проверка типа гарантирует, что сервер никогда не будет отправлять JSON с нулями. в нем или объект, свойство x которого не является числом.

@metaweta ,

Да, нам это очень нужно. См. «Ошибка на Optional в отчаянной попытке сразиться с ним.

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

Теперь, когда в TS есть союзы и охранники шрифтов, система типов достаточно мощна для этого. Вопрос в том, можно ли сделать это обратно совместимым способом. Лично я считаю, что эта функция достаточно важна, чтобы TypeScript 2.0 был обратно несовместим в этом отношении.

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

Речь идет не о _не использовании_ нулей. Речь идет о правильном моделировании всех задействованных типов. Фактически, эта функция позволила бы безопасно использовать нули - больше не было бы причин избегать их! Конечный результат будет очень похож на сопоставление с образцом для алгебраического типа Maybe (за исключением того, что это будет сделано с проверкой if, а не с выражением case)

И дело не только в нулевых литералах. null и undefined структурно одинаковы (кстати, нет функций / операторов, которые работают с одним, но не с другим), поэтому они могут быть достаточно хорошо смоделированы с помощью одного нулевого типа в TS.

@metaweta ,

Нулевое значение в JavaScript возвращается только в неподходящем регулярном выражении и при сериализации даты в JSON.

Совсем неправда.

  • Взаимодействие с DOM приводит к нулю:
  console.log(window.document.getElementById('nonExistentElement')); // null
  • Как отмечал @ aleksey-bykov выше, операции ajax могут возвращать значение null. Фактически undefined не является допустимым значением JSON:
 JSON.parse(undefined); // error
 JSON.parse(null); // okay
 JSON.stringify({ "foo" : undefined}); // "{}"
 JSON.stringify({ "foo" : null}); // '{"foo":null}'

NB: мы можем притвориться, что undefined возвращается через ajax, потому что доступ к несуществующему свойству приведет к undefined - поэтому undefined не сериализуется.

Если, однако, сервер написан на TypeScript и работает на узле, то его можно транспилировать при наличии файла .d.ts для моего внешнего кода, и проверка типа гарантирует, что сервер никогда не будет отправлять JSON с нулями. в нем или в объекте, свойство x которого не является числом.

Это не совсем так. Даже если сервер написан на TypeScipt, нельзя никоим образом гарантировать отсутствие значений NULL без проверки каждого отдельного свойства каждого отдельного объекта, полученного из постоянного хранилища.

Я вроде как согласен с @ aleksey-bykov в этом. Хотя было бы совершенно замечательно, если бы мы могли заставить TypeScript предупреждать нас во время компиляции об ошибках, вызванных null и undefined, я боюсь, что это только вызовет ложное чувство уверенности и в конечном итоге уловит мелочи, в то время как настоящие источники null останутся незамеченными.

Даже если сервер написан на TypeScipt, нельзя никоим образом гарантировать отсутствие значений NULL без проверки каждого отдельного свойства каждого отдельного объекта, полученного из постоянного хранилища.

Фактически это аргумент _для_ типов, не допускающих значения NULL. Если ваше хранилище может возвращать NULL Foo's, то тип объекта, полученного из этого хранилища, - Nullable <Foo>, а не Foo. Если после этого у вас есть функция, которая возвращает значение, которое должно возвращать Foo, тогда вы _должны_ взять на себя ответственность, обрабатывая значение null (либо вы используете его, потому что знаете лучше, либо проверяете на null).

Если бы у вас не было типов, не допускающих значения NULL, вам не обязательно приходить в голову проверять значение NULL при возврате сохраненного объекта.

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

Как вы думаете, какие не пустяки будут упущены в типах, не допускающих значения NULL?

Это не совсем так. Даже если сервер написан на TypeScipt, нельзя никоим образом гарантировать отсутствие значений NULL без проверки каждого отдельного свойства каждого отдельного объекта, полученного из постоянного хранилища.

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

Я вроде как согласен с @ aleksey-bykov в этом. Хотя было бы совершенно замечательно, если бы мы могли заставить TypeScript предупреждать нас во время компиляции об ошибках, вызванных null и undefined, я боюсь, что это только вызовет ложное чувство уверенности и в конечном итоге уловит мелочи, в то время как настоящие источники null останутся незамеченными.

Использование типов, допускающих значение NULL, не является абсолютным требованием. Если вы чувствуете, что нет необходимости моделировать случаи, когда метод возвращает null, поскольку они «несущественны», вы можете просто не использовать тип, допускающий значение null, в этом определении типа (и получить такую ​​же небезопасность, как всегда). Но нет причин думать, что этот подход не удастся - есть примеры языков, которые уже успешно реализовали его (например, Kotlin от JetBrains ).

@ aleksey-bykov Честно говоря, вы совершенно ошиблись, одна из лучших вещей в типах, не допускающих значения NULL, - это _ возможность выразить тип как тип, допускающий значение NULL_.
С вашей стратегией никогда не использовать null для предотвращения ошибки нулевого указателя, вы полностью теряете возможность использования null из-за страха перед ошибкой, что совершенно глупо.

Еще одна вещь, пожалуйста, в обсуждении языковой функции не оставляйте глупых комментариев вроде:

Типы, не допускающие значения NULL, бесполезны. Типы, не допускающие значения NULL, являются полезными значениями. Они бесполезны. Бесполезный! Вы этого не понимаете, но они вам и не нужны.

Это просто заставляет меня чувствовать, что я должен игнорировать все, что вы когда-либо публиковали в Интернете, мы здесь, чтобы обсудить функцию, я могу понять и с радостью принять, что ваша точка зрения не моя, но не ведите себя как дитя.

Я вообще не против введения аннотации ненулевого типа. Было показано, что это полезно для C # и других языков.

ОП изменил ход обсуждения следующим образом:

Честно говоря, если добавление use not-null - это цена за избежание всех исключений с нулевым указателем, я бы заплатил за это без каких-либо проблем, учитывая, что null или undefined как присваиваемые любому типу, - это худшая ошибка, которую, по моему мнению, сделал машинописный текст.

Я просто указывал на распространенность null и undefined в дикой природе.

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

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

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

1) хотите знать, может ли функция возвращать значение null (вызванное шаблоном выполнения, а не вводом).
2) хотите знать, может ли значение быть нулевым.
3) хотите знать, может ли объект данных содержать нулевые значения.

Теперь есть только два случая, в которых может возникнуть ситуация 2: если вы используете значения NULL или если функция возвращает значение NULL. Если вы удалите все нули из своего кода (при условии, что они вам не нужны), то на самом деле ситуация 2 может возникнуть только в результате ситуации 1.

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

Решение 2 также решается этим.

Это позволяет программистам, работающим в их компании, использовать процессы и стандарты для обеспечения того, чтобы нулевые типы были размечены, а не команда Typescript (точно так же, как вся система типизации является подпиской, поэтому явные типы, допускающие значение null, будут опцией в).

Что касается сценария 3, контракт между сервером и клиентом не предназначен для принудительного применения Typescript, возможность пометить затронутые значения как, возможно, нулевые, может быть улучшением, но в конечном итоге вы получите тот же гарант от этого, что и машинописный текст. инструменты дают вам все остальные ценности (то есть, никакие, если у вас нет хороших стандартов или методов кодирования!

(отправка с телефона, извините за ошибки)

@fdecampredon , в первую очередь, это не страх, дело в том, что использование null не нужно. Они мне не нужны. В качестве приятного бонуса я избавился от проблемы исключения нулевых ссылок. Как все это возможно? Используя тип суммы с пустым регистром. Sum-типы являются встроенной функцией всех языков FP, таких как F #, Scala, Haskell, и вместе с типами продуктов, называемыми алгебраическими типами данных. Стандартные примеры типов суммы с пустым регистром могут быть Optional из F # и Maybe из Haskell. TypeScript не имеет ADT, но вместо этого в нем обсуждается возможность добавления значений, не допускающих значения NULL, которые моделируют один особый случай того, что охватили бы ADT. Итак, мое сообщение: откажитесь от значений, не допускающих значения NULL, для ADT.

@spion , Плохие новости: F # получил нули (как наследие .NET). Хорошие новости: ими никто не пользуется. Как можно не использовать null, когда он есть? У них есть Optionals (как и у последней версии Java, о которой вы упомянули). Так что вам не нужен null, если в вашем распоряжении есть лучший выбор. Это то, что я в конечном итоге предлагаю: не трогать нули (ненулевые), просто забыть об их существовании и реализовать ADT как языковую функцию.

Мы используем null, но не так, как вы. Исходный код моей компании состоит из двух частей.

1) Когда дело доходит до данных (например, данных базы данных), мы заменяем null пустыми данными во время объявления переменной.

2) Все остальные, такие как программируемые объекты, мы используем null, чтобы знать, когда есть ошибка и лазейка в исходном коде, где объекты не создаются или не назначаются, не являясь необходимыми объектами javascript. Undefined - это проблемы с объектами javascript, в которых есть ошибка или лазейка в исходном коде.

Данные, которые мы не хотим обнулять, потому что это данные клиентов, и они увидят пустые формулировки.

Машинописный текст @ aleksey-bykov имеет adt с типом union, единственное, чего не хватает, - это сопоставление с образцом, но эта функция просто не может быть реализована с философией машинописного текста для создания javascript, близкого к исходному источнику.

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

@fdecampredon , TS не имеет ADT, у него есть объединения, которые не являются типами сумм, потому что, как вы сказали, 1. они не могут правильно моделировать пустой кейс, поскольку нет типа единицы, 2. нет надежного способа деструктуризации их

сопоставление шаблонов для ADT может быть реализовано способом, который тесно связан с сгенерированным JavaScript, в любом случае я надеюсь, что этот аргумент не является поворотным моментом

@ aleksey-bykov это не F #. Это язык, предназначенный для моделирования типов JavaScript. Библиотеки JavaScript используют нулевые и неопределенные значения. Следовательно, эти значения следует моделировать соответствующими типами. Поскольку даже ES6 не поддерживает алгебраические типы данных, нет смысла использовать это решение с учетом целей проектирования TypeScript.

Кроме того, программисты на JavaScript обычно используют проверки if (в сочетании с проверками typeof и на равенство) вместо сопоставления с образцом. Они уже могут сузить типы объединения TypeScript. С этого момента это всего лишь крошечный шаг к поддержке типов, не допускающих значения NULL, с преимуществами, сравнимыми с алгебраическим Maybe и т. Д.

Я удивлен, что никто на самом деле не упомянул огромные изменения, которые может потребоваться внести lib.d.ts и потенциальные проблемы временного нулевого состояния полей класса во время вызова конструктора. Это некоторые реальные, актуальные потенциальные проблемы для реализации типов, не допускающих значения NULL ...

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

declare function getName(personId:number):string|null;

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

function doSomethingWithPersonsName(personId:number) {
  var name = getName(personId);
  if (name != null) return doThingsWith(name); // type guard narrows string|null to just string
  else { return handleNullCase(); }
}

И теперь ты молодец! Система типов гарантирует, что doThingsWith будет вызываться с именем, отличным от NULL.

function doThingsWith(name:string) {
  // Lets create some funny versions of the name
  return [uppercasedName(name), fullyLowercased(name), funnyCased(name)]
}

Ни одна из этих функций не нуждается в проверке на нуль, и код по-прежнему будет работать без выброса. И как только вы попытаетесь передать строку, допускающую значение NULL, одной из этих функций, система типов немедленно сообщит вам, что вы сделали ошибку:

function justUppercased(personId:number) {
  var name = getName(personId);
  return uppercasedName(name); // error, passing nullable to a function that doesn't check for nulls.
}

Это огромное преимущество: теперь система типов отслеживает, могут ли функции обрабатывать значения, допускающие значение NULL, и, кроме того, отслеживает, действительно ли это нужно им или нет. Более чистый код, меньше проверок, больше безопасности. И речь идет не только о строках - с помощью библиотеки, такой как проверки типов во время выполнения, вы также можете создать защиту типов для гораздо более сложных данных.

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

declare function getName(personId:number):string;

и в этих случаях машинописный текст предупредит вас только в том случае, если вы сделаете что-то явно неправильное.

uppercasedName(null);

Минусов честно говоря, кроме обратной совместимости, не вижу.

@fdecampredon Профсоюзы - это всего лишь союзы. Они не являются "несовпадающими" союзами, иначе говоря, суммами. См. № 186.

@ aleksey-bykov

Обратите внимание, что добавление типа опции по-прежнему будет серьезным изменением.

// lib.d.ts
interface Document {
    getElementById(id: string): Maybe<Element>;
}

...

// Code that worked with 1.3
var myCanvas = <HTMLCanvasElement>document.getElementById("myCanvas");
// ... now throws the error that Maybe<Element> can't be cast to an <HTMLCanvasElement>

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

class Option<T> {
    hasValue: boolean;
    value: T;
}

var { hasValue, myCanvas: value } = <Option<HTMLCanvasElement>> $("myCanvas");
if (!hasValue) {
    throw new Error("Canvas not found");
}
// Use myCanvas here

но единственное значение из этого - если lib.d.ts (и любые другие .d.ts и вся ваша кодовая база, но мы предполагаем, что мы можем это исправить) также используют его, иначе вы снова не узнаете, есть ли функция, которая не использует Option, может возвращать null или нет, если вы не посмотрите на ее код.

Обратите внимание, что я также не сторонник того, чтобы типы по умолчанию были ненулевыми (по крайней мере, для TS 1.x). Это слишком большая перемена.

Но допустим, мы говорим о 2.0. Если мы все равно собираемся внести критическое изменение (добавление типов параметров), почему бы также не сделать типы не допускающими значения NULL по умолчанию? Создание типов, не допускающих значения NULL по умолчанию, и добавление типов параметров не являются исключительными. Последний может быть автономным (например, в F #, как вы указываете), но для первого требуется второе.

@Arnavion , есть какое-то недоразумение, я не говорил, что нам нужно заменить подписи, все существующие подписи остаются нетронутыми, это для новых разработок, которые вы можете использовать либо с ADT, либо с чем угодно. Так что никаких критических изменений. По умолчанию ничего не делается ненулевым.

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

@ aleksey-bykov

Я не говорил, что нужно заменить подписи, все существующие подписи остаются нетронутыми

_I_ сказал, что подписи нужно заменить, и причину я уже назвал:

но единственное значение из этого - если lib.d.ts (и любые другие .d.ts и вся ваша кодовая база, но мы предполагаем, что мы можем это исправить) также используют его, иначе вы снова не узнаете, есть ли функция, которая не использует Option, может возвращать null или нет, если вы не посмотрите на ее код.


По умолчанию ничего не делается ненулевым. Всегда.

Для TS 1.x я согласен, только потому, что это слишком серьезное изменение. В версии 2.0 использование типа параметра в сигнатурах по умолчанию (lib.d.ts и т. Д.) Уже было бы критическим изменением. Создание типов, не допускающих значения NULL, по умолчанию _ в дополнение к этому_ становится оправданным и не имеет никаких недостатков.

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

Итак, вы говорите, что если у меня есть функция foo, которая возвращает Option <number>, и функциональная панель, которая возвращает число, мне не разрешено быть уверенным, что bar не может вернуть null, если я не посмотрю на реализацию bar или не поддержу документацию «Эта функция никогда не возвращает null»? Вам не кажется, что это наказывает функции, которые никогда не возвращают null?

функция bar из вашего примера с незапамятных времен была известна как допускающая значение NULL, она использовалась примерно в 100500 приложениях, и все рассматривали результат как допускающий значение NULL, теперь вы подошли и заглянули внутрь и обнаружили, что null невозможно, означает ли это, что вы должны пойти дальше и изменить подпись с допускающей значение NULL на не допускающую значение NULL? Думаю, не стоит. Потому что эти знания хоть и ценные, но не стоят того, чтобы тормозить 100500 приложений. Что вам нужно сделать, так это создать новую библиотеку с исправленными подписями, которая будет делать это так:

old_lib.d.ts

...
declare function bar(): number; // looks like can return a potentially nullable number
...

revised_lib.d.ts

declare function bar(): !number; // now thank to the knowledge we are 100% certain it cannot return null

теперь устаревшие приложения продолжают использовать old_lib.d.ts , для новых приложений разработчик может выбрать revised_libs.d.ts

К сожалению, в revised_libs.d.ts есть 50 других функций, на которые я еще не смотрел, и все они возвращают число (но я не знаю, действительно ли это число или число, допускающее значение NULL). Что теперь?

Что ж, не торопитесь, попросите о помощи, используйте управление версиями (в зависимости от уровня знаний, которые вы уже приобрели, вы можете выпускать его постепенно с постоянно увеличивающимся номером версии: revised_lib.v-0.1.12.d.ts )

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

Я не фанат! только потому, что набирать больше багажа (как с точки зрения нажатия клавиш, так и необходимости не забывать его использовать). Если мы хотим, чтобы в 1.x не допускалось значение NULL, тогда! - это один из вариантов, уже обсужденных выше, но я бы все же сказал, что наличие возможного критического изменения с 2.0 и включение недопустимости обнуления по умолчанию того стоит.

С другой стороны, возможно, это приведет к ситуации в стиле Python 2/3, когда никто не обновляется до TS 2.0 в течение многих лет, потому что они не могут позволить себе пройти через свою кодовую базу в миллион строк, убедившись, что каждое объявление переменной и класс параметр члена и функции и ... помечен знаком? если он может быть нулевым. Даже 2to3 (инструмент миграции Python 2 на 3) не должен иметь дело с такими широкомасштабными изменениями.

Сможет ли 2.0 стать решающим изменением, зависит от команды TS. Я бы проголосовал за "да", но тогда у меня нет кодовой базы в миллион строк, которую нужно было бы исправить, так что, возможно, мой голос не учитывается.

Возможно, нам следует спросить ребят из Funscript, как они согласовывают DOM API, возвращающий значения NULL, с F # (Funscript использует библиотеки TypeScript lib.d.ts и другие .d.ts для использования из кода F #). Я никогда не использовал его, но, глядя на http://funscript.info/samples/canvas/index.html, кажется, что поставщик типа не думает, что document.getElementsByTagName ("canvas") [0] может когда-либо быть неопределенный.

Изменить: здесь кажется, что document.getElementById () не должен возвращать null. По крайней мере, похоже, что он не возвращает Option <Element>, видя, что он обращается к результату .onlick.

@spion
Спасибо, я об этом не подумал.

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

@Griffork взглянул на этот отфильтрованный список проблем в компиляторе машинописного текста, чтобы оценить, насколько большую пользу может принести это изменение. Всех «аварийных» ошибок, перечисленных по этой ссылке, можно было бы избежать, используя типы, не допускающие значения NULL. И мы говорим здесь об удивительном уровне стандартов, коммуникации и анализа кода Microsoft.

Что касается поломки, я думаю, что если вы продолжите использовать существующие определения типов, возможно, вы вообще не получите никаких ошибок, за исключением того, что компилятор укажет потенциально неинициализированные переменные и просачивающиеся поля. С другой стороны, вы можете получить много ошибок, особенно, например, если поля класса часто остаются неинициализированными в конструкторах вашего кода (которые будут инициализированы позже). Поэтому я понимаю ваши опасения и не настаиваю на обратном несовместимости изменений для TS 1.x. Я все еще надеюсь, что мне удалось убедить вас в том, что если какое-либо изменение в языке было достойным нарушения обратной совместимости, то это оно.

В любом случае, у Facebook Flow есть типы, не допускающие значения NULL. Когда он станет более зрелым, возможно, стоит изучить его как замену TS для тех из нас, кому небезразлична эта проблема.

@spion , единственное число, которое дает нам ваш список, - это сколько раз null или undefined были упомянуты по какой-либо причине, в основном это только говорит о том, что говорилось о null и undefined, я надеюсь, ясно, что вы не можете использовать его в качестве аргумента

@ aleksey-bykov Я не являюсь - я рассмотрел _все_ проблемы в этом отфильтрованном списке, и каждая из них, в которой было слово «сбой», была связана с трассировкой стека, которая показывает, что функция пыталась получить доступ к свойству или методу неопределенного или нулевого значения.

Я попытался сузить фильтр разными ключевыми словами, и мне (кажется, мне) удалось получить их все.

@spion
Вопрос: сколько из этих ошибок возникает в тех местах, где им нужно было бы пометить переменную как допускающую значение NULL или неопределенную?

EG. Если объект может иметь родителя, и вы инициализируете родительский объект равным нулю, и у вас всегда будет один объект с нулевым родителем, вам все равно придется объявить родительский объект как возможно равным нулю.
Проблема здесь в том, что программист пишет код с предположением, что цикл всегда прерывается, прежде чем достигнет нулевого родителя. Это не проблема, если в языке присутствует null, то же самое произойдет и с undefined .

Причины, по которым null остается таким же простым в использовании, как сейчас:
• Это лучшее значение по умолчанию, чем undefined.
. 1) в некоторых случаях быстрее проверять (наш код должен быть очень производительным)
. 2) это позволяет циклам in работать с объектами более предсказуемо.
. 3) это делает использование массива более разумным при использовании нулей для пустых значений (в отличие от отсутствующих значений). Обратите внимание, что delete array[i] и array[i] = undefined имеют разное поведение при использовании indexOf (и, возможно, других популярных методов массива).

Я считаю, что результат создания нулей требует дополнительной пометки для использования в языке:
• Я получил неопределенную ошибку вместо нулевой ошибки (что случается в большинстве сценариев машинописного текста).

Когда я сказал, что у нас нет проблемы с экранированием нулей, я имел в виду, что переменные, которые не инициализированы равными нулю, никогда не становятся нулевыми, мы по-прежнему получаем ошибки нулевого исключения точно в том же месте, где мы получим неопределенные ошибки (как и команда Typescript ). Если усложнить использование null (потребовать дополнительного синтаксиса) и оставить значение undefined таким же, на самом деле возникнет больше проблем для некоторых разработчиков (например, нас).

Добавление дополнительного синтаксиса для использования null означает, что в течение нескольких недель / месяцев разработчики, которые используют много значений null, будут делать ошибки слева, справа и по центру, пытаясь запомнить новый синтаксис. И это будет еще один способ ошибиться в будущем (немного некорректно аннотируя что-то). [Пора отметить, что я ненавижу идею использования символов для представления типов, это делает язык менее понятным]

Пока вы не объясните мне ситуацию, в которой null вызывает ошибку или проблему, которой не будет undefined, я не соглашусь с тем, что использование null будет значительно сложнее, чем undefined. Она имеет свой случай использования, и только потому , что это не помогает вам, не означает , что нарушение изменения , которые вы хотите (что _will_ больно рабочий процесс других разработчиков) должны идти вперед.

Вывод:
Нет смысла в возможности объявлять типы, не допускающие значения NULL, без возможности определения типов, не допускающих неопределенности. Неопределенные типы невозможны из-за того, как работает javascript.

@Griffork, когда я говорю, что не Facebook Flow как доказательство того, что это вполне выполнимо.

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

@spion
Я действительно верю, что наконец понимаю, откуда вы.

Я считаю, что вам нужны средства защиты типов, которые помогут определить, когда значение не может быть нулевым, и позволят вам вызывать функции, которые не проверяют наличие нуля внутри. А если убрать защитный if-оператор, это станет ошибкой.

Я вижу, что это полезно.

Я также считаю, что это не совсем та серебряная пуля, на которую вы надеетесь.

Компилятор, который не запускает ваш код и не проверяет все его аспекты, не будет лучше определять, где находятся неопределенные / нулевые значения, чем программист, написавший код. Я обеспокоен тем, что это изменение убаюкивает людей ложным чувством безопасности и фактически затрудняет отслеживание ошибок null / undefined, когда они действительно возникают.
На самом деле, я думаю, что решение, которое вам нужно, - это хороший набор инструментов для тестирования, поддерживающих Typescript, которые могут воспроизводить подобные ошибки в Javascript, вместо того, чтобы реализовывать тип в компиляторе, который не может выполнить свои обещания.

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

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

«Неопределенные значения, как и null, тоже могут вызывать проблемы. К сожалению, неопределенные значения повсеместно встречаются в JavaScript, и их трудно избежать, не сильно влияя на удобство использования языка [...] Flow в этом случае идет на компромисс: он обнаруживает неопределенные локальные переменные и возвращаемые значения, но игнорирует возможность неопределенности в результате доступа к свойствам объекта и элементам массива ".

Теперь undefined и null работают по-разному, неопределенные ошибки все еще могут появляться повсюду, знак вопроса не может гарантировать, что значение не равно null, а язык ведет себя иначе, чем Javascript (это то, чего TS пытается избежать из того, что я видел ).

пс

foo(thing: whatiknowaboutmyobject) {
    if (thing.hidden) {
        delete thing.description;
    }
}

if (typeof thing.description === "string") {
    //thing.description is non-nullable now, right?
    foo(thing);
    //What is thing.description?
    console.log(thing.description.length);
}

TS уже уязвим для эффектов псевдонима (как и любой язык, допускающий изменяемые значения). Это будет компилироваться без ошибок:

function foo(obj: { bar: string|number }) {
    obj.bar = 5;
}

var baz: { bar: string } = { bar: "5" };

foo(baz);

console.log(baz.bar.charAt(0)); // Runtime error - Number doesn't have a charAt method

Документы Flow заявляют это только для полноты.

Изменить: лучший пример.

Старый:

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

Я имею в виду,

mything.mystring = 5; // is clearly wrong.
delete mything.mystring; //is not clearly wrong - this is not quite the equivalent of setting mystring to >undefined.

Редактировать:
Мех, на данный момент это в значительной степени личные предпочтения. После долгого использования javascript я не думаю, что это предложение поможет языку. Я думаю, что это убаюкивает людей ложным чувством безопасности, и я думаю, что это оттолкнет Typescript (как язык) от Javascript.

@Griffork В качестве примера того, как текущий машинописный текст вводит вас в ложное чувство безопасности, попробуйте пример, который вы представили на игровой площадке:

var mything = {mystring: "5"}; 
delete mything.mystring;
console.log(mything.mystring.charAt(1));

Кстати, с оператором удаления можно обращаться так же, как с присвоением значения типа null, и этого было бы достаточно, чтобы охватить и ваш случай.

Утверждение, что язык будет вести себя иначе, чем JavaScript, верно, но бессмысленно. TypeScript уже имеет поведение, отличное от JavaScript. Суть системы типов всегда заключалась в том, чтобы запретить программы, которые не имеют смысла. Моделирование типов, не допускающих значения NULL, просто добавляет пару дополнительных ограничений. Запрещение присвоения значений null или undefined переменной типа, не допускающего значения NULL, в точности то же самое, что запрещение присвоения числа переменной типа string. JS допускает и то, и другое, TS не допускает ни того, ни другого.

@spion

идея состоит в том, чтобы избежать нулевых проверок повсюду в вашем коде

Если я понимаю, что вы защищаете:

A. Сделайте все типы ненулевыми по умолчанию.
B. Отметьте поля и переменные, допускающие значение NULL.
C. Убедитесь, что разработчик приложения / библиотеки проверил все точки входа в приложение.

Но не означает ли это, что ответственность за то, чтобы код не содержал нулей, лежит на человеке, пишущем код, а не на компиляторе? Фактически мы говорим компилятору: «dw, я не допускаю никаких нулей в систему».

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

Дело в том, что последний подход склонен к исключениям с нулевой ссылкой, но он более правдив. Притворяться, что поле объекта, полученного по сети (например, ajax), не равно нулю, подразумевает веру в Бога: smiley:.

Я считаю, что есть серьезные разногласия по этому поводу, потому что, в зависимости от того, над чем вы работаете, пункт C выше может быть тривиальным или невыполнимым.

@jbondc Я рад, что вы спросили об этом. Действительно, CallExpression отмечен как неопределенный или допускающий значение NULL. Однако система типов в настоящее время не пользуется этим преимуществом - она ​​по-прежнему разрешает все операции с typeArguments как если бы он не был нулевым или неопределенным.

Однако при использовании новых типов объединения в сочетании с типами, не допускающими значения NULL, тип может быть выражен как NodeArray<TypeNode>|null . Тогда система типов не разрешит никаких операций с этим полем, если не будет применена нулевая проверка:

if (ce.typeArguments != null) {
  callSomethingOn(ce.typeArguments)
}

// callSomethingOn doesn't need to perform any checks

function callSomethingOn(na:NodeArray<TypeNode>) {
...
}

С помощью средств защиты типа TS 1.4 внутри блока if тип выражения будет сужен до NodeArray<TypeNode> что, в свою очередь, разрешит все операции NodeArray с этим типом; кроме того, все функции, вызываемые в рамках этой проверки, смогут указать свой тип аргумента как NodeArray<TypeNode> без выполнения каких-либо дополнительных проверок.

Но если вы попытаетесь написать

function someOtherFunction(ce: CallExpression) {
  callSomethingOn(ce.typeArguments)
}

компилятор предупредит вас об этом во время компиляции, и ошибки просто не было бы.

Так что нет, @NoelAbrahams , дело не в том, чтобы знать все наверняка. Речь идет о компиляторе, который поможет вам определить, какое значение может содержать переменная или поле, как и для всех других типов.

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

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

Я нахожу удивительным, что существует так много неправильных представлений о типах, не допускающих значения NULL. Это просто типы, которые нельзя оставить неинициализированными или которым нельзя присвоить значения null или undefined . Это не так уж и похоже на невозможность присвоить строку номеру. Это мой последний пост по этому вопросу, так как я чувствую, что ничего не добился. Если кому-то интересно узнать больше, я рекомендую начать с видео «Ошибка на миллиард долларов», о котором я упоминал выше. Проблема хорошо известна и успешно решается многими современными языками и компиляторами.

@spion , я полностью согласен со всеми преимуществами возможности указать, может ли тип иметь значение null. Но вопрос в том, хотите ли вы, чтобы типы были ненулевыми _по умолчанию _?

Притворяться, что поле объекта, полученного по сети (например, ajax), не равно NULL, подразумевает веру в Бога.

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

Конечно. Это сводится к тому, хотим ли мы пометить поле как допускающее значение NULL (в системе, где поля не имеют значения NULL по умолчанию) или хотим ли мы пометить поле как поле, не допускающее значения NULL (в системе, где поля по умолчанию допускают значение NULL).

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

@NoelAbrahams Я не понимаю, почему это `` намеренно '' в основном большую часть времени, когда вы не хотите иметь значение null, также когда точка входа может возвращать значение null, вам нужно будет это проверить , в конце концов, система типов с ненулевым типом по умолчанию позволяет выполнять меньшую нулевую проверку, потому что вы сможете доверять некоторым api / библиотеке / точке входа в своем приложении.

Если вы немного задумаетесь о том, что маркировка типа как ненулевого в системе типов, допускающих значение NULL, имеет ограниченное значение, вы все равно сможете использовать типизированную переменную / возвращаемый тип, допускающую значение NULL, без необходимости ее тестирования.
Это также заставит автора определения написать больше кода, поскольку большую часть времени хорошо спроектированная библиотека никогда не возвращает значение null или undefined.
Наконец, даже концепция странная: в системе типов с типом, не допускающим значения NULL, тип, допускающий значение NULL, прекрасно выражается как тип объединения: ?string эквивалентен string | null | undefined . В системе типов с типом, допускающим значение NULL по умолчанию, где вы можете пометить тип как не допускающий значение NULL, как бы вы выразили !string ? string - null - undefined ?

В конце концов, я не совсем понимаю, что здесь беспокоит людей, null не является строкой, точно так же, как 5 не является строкой, оба значения не могут быть будет использоваться там, где ожидается строка, а пропуск ошибки var myString: string = null подвержен ошибкам: var myString: string = 5 .
Возможность присвоения null или undefined любому типу - это, возможно, концепция, с которой разработчик знаком, но она все еще плохая.

Я не думаю, что был полностью прав в своем предыдущем посте: я виню в этом час.

Я только что просмотрел часть нашего кода, чтобы увидеть, как все будет работать, и, безусловно, помогло бы пометить определенные поля как допускающие значение NULL, например:

interface Foo {
        name: string;
        address: string|null; /* Nullable */
}

var foo:Foo = new FooClass();
foo.name.toString(); // Okay
foo.address.toString(); // Error: do not use without null check

Но я возражаю против следующего:

foo.name = undefined; // Error: non-nullable

Я чувствую, что это помешает естественному способу работы с JavaScript.

То же самое можно сказать и о номере:

interface Foo {
        name: string;
        address: string|number; 
}
var foo:Foo = new FooClass();
foo.name.toString(); // Okay
foo.address.slice() // error

foo.name  = 5 // error

И это все еще актуально в JavaScript

Разумно, сколько раз вы добровольно присваиваете null свойству объекта?

Я думаю, что большинство вещей было бы помечено как null, но вы бы полагались на охранников типов, чтобы объявить, что поле теперь не допускает значения NULL.

@fdecampredon
На самом деле довольно много.

@Griffork ,

Я думаю, что большинство вещей было бы помечено как null

Это была моя первоначальная мысль. Но после просмотра некоторых разделов нашего кода я обнаружил такие комментарии, как:

interface MyType {

     name: string;

     /** The date the entry was updated from Wikipedia or undefined for user-submitted content. */
     wikiDate: Date; /* Nullable */
}

Идея о том, что поле допускает значение NULL, часто используется для предоставления информации. И TypeScript будет обнаруживать ошибки, если для доступа к wikiDate требуется защита типа.

@fdecampredon

foo.name = 5 // error
И это все еще актуально в JavaScript

Верно, но это ошибка, потому что TypeScript со 100% уверенностью знает, что это не было намеренно.

в то время как

foo.name = undefined; // Не отправлять имя на сервер

совершенно преднамеренно.

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

 wikiDate: ?Date;

Я согласен с @NoelAbrahams

foo.name = 5 // ошибка
И это все еще актуально в JavaScript
Верно, но это ошибка, потому что TypeScript со 100% уверенностью знает, что это не было намеренно.

Компилятор просто знает, что вы отметили имя как string а не string | number если вам нужно значение, допускающее значение NULL, вы просто отметите его как ?string или string | null (что в значительной степени эквивалентно)

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

wikiDate:? Дата;

Таким образом, мы согласны с тем, что тип по умолчанию не равен NULL, и вы должны пометить значение NULL как ? :).
Обратите внимание, что это будет тип объединения, поскольку ?Date будет эквивалентом Date | null | undefined :)

Ой, извините, я пытался согласиться с обнуляемым значением по умолчанию, а не с нулевым значением со специальной типизацией (символы сбивают с толку).

@fdecampredon , на самом деле это означает, что когда поле или переменная помечены как допускающие значение NULL, для доступа требуется защита типа:

var wikiDate: ?Date;

wikiDate.toString(); // error
wikiDate && wikiDate.toString(); // okay

Это не критическое изменение, потому что мы все еще можем это сделать:

 var name: string;   // okay
 name.toString();  // if you think that's fine then by all means

Возможно, вы считаете, что этого не может быть без добавления null в типы объединения?

Ваш первый пример абсолютно верен, когда вы это делаете:

wikiDate && wikiDate.toString(); // okay

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

Однако ваш второй пример не хорош

var name: string;   // okay
name.toString();  // if you think that's fine then by all means

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

var name: string;   // okay
name.toString();  // error because not initialized
var name: string;
if (something) {
  name = "Hello World";
} else {
  name = "Foo bar";
}
name.toString();  // no error since name will always be initialized.

Я не знаю точно, где поставить барьер, но это определенно потребует какой-то тонкой настройки, чтобы не мешать разработчику.

Это критическое изменение и не может быть введено до версии 2.0, за исключением, возможно, директивы use nonnull, предложенной

Почему бы не иметь:

var string1: string; //this works like typescript does currently, doesn't need type-guarding before use, null and undefined can be assigned to it.
string1.length; //works
var string2: !string; //this doesn't work because the string must be assigned to a non-null and non-undefined value, doesn't need type-guarding before use.
var string3: ?string; //this must be type guarded to non-null, non-undefined before use.
var string4: !string|null = null; //This may be null, but should never be undefined (and must be type-guarded before use).
var string5: !string|undefined; //this may never be null, but can be undefined (and must be type-guarded before use).

И у вас есть флаг компилятора (который работает, только если включен -noimplicitany), который говорит -noinferrednulls, который отключает нормальный синтаксис для типов (например, string и int), и вы должны указать? или ! с ними (null, undefined и любые типы, являющиеся исключениями).

Таким образом, значения, не допускающие значения NULL, являются подпиской, и вы можете использовать флаг компилятора, чтобы принудительно обнулить проект.
Компилятор отмечает ошибки при присвоении типов , а не после (как в предыдущем предложении).

Мысли?

Изменить: я написал это, потому что это заставляет идею использования ненулевого значения быть явным в каждом действии. Любой, кто читает код любого другого проекта TS, точно знает, что происходит. Также флаг компилятора становится очень очевидным, если он включен (поскольку blah: string является ошибкой, а blah:!string - нет, аналогично тому, как работает -noimplicitany).

Edit2:
После этого DefinatelyTyped можно было бы обновить для поддержки noninferrednulls, и они не изменит использование библиотек, если люди решат не подписываться на? и ! особенность.

Меня не волнует, являются ли ненулевые и не неопределенные параметры согласия, отказа, с
модификаторы типа (!?), директива или флаг компилятора; Я сделаю все, что угодно
требуется, чтобы получить их настолько долго, насколько они могут быть выражены, что не является
в настоящее время дело.

В понедельник, 22 декабря 2014 г., в 14:35, Griffork [email protected] написал:

Почему бы не иметь:

var string1: строка; // это работает так же, как в настоящее время машинописный текст, не требует защиты типов перед использованием, ему могут быть присвоены значения null и undefined.
string1.length; // работает var string2:! string; // это не работает, потому что строка должна быть присвоена ненулевому и не неопределенному значению, не требует защиты типов перед использованием. var string3:? string; // это должно быть типом, защищенным от ненулевого, не неопределенного перед использованием. var string4:! string | null = null; // Это может быть пустое значение, но никогда не должно быть неопределенным (и перед использованием должно быть защищено типом) .var string5:! String | undefined; // он никогда не может быть нулевым, но может быть неопределенным (и перед использованием должен быть защищен типом).

И иметь флаг компилятора (который работает, только если включен -noimplicitany), который
говорит -noinferrednulls, что отключает нормальный синтаксис для типов (например,
string и int), и вы должны указать? или ! с ними (null, undefined
и любые исключения).

Таким образом, значения, не допускающие значения NULL, являются подпиской, и вы можете использовать компилятор
флаг, чтобы принудительно обнулить проект.
Компилятор отмечает ошибки при _ присвоении типов_, а не после (например,
предыдущее предложение).

Мысли?

Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -67899445
.

Майк Стей - [email protected]
http://www.cs.auckland.ac.nz/~mike
http://reperiendi.wordpress.com

@Griffork, это был бы вариант, но, на мой взгляд, плохой, и я объясню, почему:

  • Это вызовет гораздо больше работы в файлах определений, поскольку теперь нам придется проверять и аннотировать каждый тип, чтобы иметь правильное определение.
  • Мы закончим тем, что напишем !string (а иногда и ?string ) везде в коде, что сделает код намного менее читаемым.
  • !string в системе, где тип допускает значение NULL, - это странная концепция, единственный способ, которым вы не можете ее описать, - это string minus null minus undefined , напротив, ?string довольно просто описать в система типов, в которой тип по умолчанию равен нулю string | null | undefined .
  • Я предвижу большую головную боль (и потерю производительности), чтобы найти алгоритм проверки типа, при котором компилятор понимает, что string | null требует защиты типа, а string - нет, вы в основном вводите концепцию, в которой с одним типом союза следует обращаться иначе, чем с другим.
  • И, наконец, самое худшее: мы полностью теряем вывод типа var myString = "hello" какой должна быть myString string , ?string или !string ? честно говоря, здесь большая головная боль в перспективе.

Если по умолчанию у нас нет ненулевого типа, лучшее предложение, которое я видел здесь, - это директива 'use non-null' предложенная @metaweta.
Конечно, это нужно четко указать, но, по крайней мере, с помощью строки use non-null во всем нашем файле мы можем получить простое предсказуемое поведение.

@fdecampredon

  1. В файлах определений может быть намного больше работы, но _ вам все равно придется проделать эту работу_ (чтобы убедиться, что типы верны), и на этот раз компилятор напомнит вам о том, что вы еще не редактировали (если вы используете -noimplicitnull ).
  2. Я открыт для других предложений по аннотации. Я искренне верю, что существующая система типов имеет свое место и ее не следует заменять. Я не думаю, что серьезное изменение того стоит. Вместо этого я думаю, мы должны найти лучший способ описать то, что вам нужно. (Мне очень, очень не нравится идея представлять все это с помощью символов, они не интуитивно понятны.)
  3. Что в этом сложно описать? Я видел обсуждения в другом месте в машинописном тексте, где предлагался этот запрос (для определенных типов) (без маркера). Сделал быстрый поиск, и я не смог найти проблему, я поищу позже.

  4. Если вы имеете в виду то, что я написал как !string|null , это будет работать в текущей системе, если null будет обрабатываться _like_ {} (но не может быть ему назначен).
    Если вы говорите о string|null которого у меня не было в моем списке, то я думаю, что в этом случае следует игнорировать null. Нулевые и неопределенные значения имеют смысл только в союзах, где перед каждым ненулевым и ненулевым значением предшествует! и any не является никаким (это может быть предупреждение / ошибка компилятора).
  5. Хороший вопрос, и тот, который возникает только в том случае, если вы используете параметр -noimplicitnull, я думаю, что самым безопасным вариантом было бы назначить его тому, какой из вариантов с наибольшей вероятностью вызовет раннюю ошибку (возможно, обнуляемую), но я получаю чувство, что есть лучшая идея, о которой я не думаю. Интересно, есть ли у кого-то еще предложение, как к этому подойти?

Изменить: добавлено в пункт 2.
Изменить: исправлена ​​опечатка в пункте 4.

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

Да и нет, проверьте библиотеку, которую вы используете, и посмотрите, сколько на самом деле возвращает null или undefined.
это довольно редкий случай, мы смогли найти в этой проблеме очень мало случаев для стандартной библиотеки, и, например, библиотека Promise никогда этого не делает.
Я хочу сказать, что в системе типов, где тип по умолчанию не допускает значения NULL, большинство существующих файлов определений уже действительны .

Я открыт для других предложений по аннотации. Я искренне верю, что существующая система типов имеет свое место и не подлежит замене. Я не думаю, что серьезное изменение того стоит. Вместо этого я думаю, мы должны найти лучший способ описать то, что вам нужно. (Мне очень, очень не нравится идея представлять все это с помощью символов, они не интуитивно понятны.)

Я не думаю, что есть такой способ, но надеюсь, что ошибаюсь, потому что, на мой взгляд, он имеет неоценимую ценность.
Что касается критического изменения, почему вы так против директивы 'use non-null' ?
Разработчики, которые хотят использовать систему типов, допускающих значение NULL, не пострадают (если только они уже не добавят 'use non-null' в начало своего файла, но, честно говоря, это было бы немного ... странно)
А разработчики, которым нужна система ненулевых типов, могут просто использовать новую директиву.

Что в этом сложно описать? Я видел обсуждения в другом месте в машинописном тексте, где предлагался этот запрос (для определенных типов) (без маркера). Сделал быстрый поиск, и я не смог найти проблему, я поищу позже.
Я просто считаю эту концепцию немного «странной» и не очень понятной. Я обычно использую композицию в качестве основного инструмента для программирования, а не _decomposition_, но почему бы и нет.

Если вы имеете в виду то, что я написал как! String | null, это сработало бы в текущей системе, если бы null обрабатывался как {} (но не мог быть ему назначен). Если вы говорите о строке | null, которой у меня не было в моем списке, то я думаю, что в этом случае null следует игнорировать. Нулевые и неопределенные значения имеют смысл только в союзах, где перед каждым ненулевым и ненулевым значением предшествует! и any не является никаким (это может быть предупреждение / ошибка компилятора).
Я имею в виду то, что при раскладке на 3 типа:

  • !sting : string - null - undefined
  • string : string | null | undefined
  • ?string : string | null | undefined

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

Хороший вопрос, и тот, который возникает только в том случае, если вы используете параметр -noimplicitnull, я думаю, что самым безопасным вариантом было бы назначить его тому, какой из вариантов с наибольшей вероятностью вызовет раннюю ошибку (возможно, обнуляемую), но я получаю чувство, что есть лучшая идея, о которой я не думаю. Интересно, есть ли у кого-то еще предложение, как к этому подойти?

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

Опять же, почему вы против директивы 'use non-null' , чем больше я думаю об этом, тем больше мне кажется идеальным решением.

@fdecampredon
Поскольку предложенное в настоящее время «использовать ненулевое значение» меняет способ _использования_ языка, а не _писание_ языка. Это означает, что функция из одного места при перемещении в другое может работать по-разному, когда она _используется_. Вы получите ошибки времени компиляции, которые потенциально находятся на расстоянии 1-3 файлов, потому что что-то набрано неправильно.

Разница между:
string
и
?string
Это что? и ! символ требует строгой проверки типа для этой переменной (во многом как ваша директива "use nonnull", но для каждой переменной). Объясните это таким образом, и я думаю, у вас не будет особых проблем с людьми, которые это поймут.

Разница, конечно, в следующем:
Файл 1:

//...187 lines of code down...
string myfoo(checker: boolean) {
    if(checker){
        return null;
    }
    else {
        return "hello";
    }
}

Файл 2:

"use nonnull"
//...2,748 lines of code down...
string myfoo(checker: boolean) {
    if(checker){
        return null; //Error!
    }
    else {
        return "hello";
    }
}

Разработчики теперь должны держать в уме карту того, какие файлы ненулевые, а какие нет. Я искренне считаю, что это плохая идея (даже если большая часть _вашего_ кода "использует ненулевое значение").

Изменить: Также, когда вы начинаете вводить функцию и получаете маленькое окошко, которое сообщает вам, что такое определение функции, как узнать, является ли string в этом определении допускающим значение NULL или не допускающим значения NULL?

@Griffork

  • опять же, это уже имеет место с 'use strict' .
  • По крайней мере, идея проста. и ввести простую концепцию в систему типов.
  • Если разработчик перемещает функцию в другой файл, мне кажется, что это довольно крайний случай, и поскольку ошибка, которую ему предложат, будет связана с нулевой проверкой, он быстро сможет понять, что происходит ...

Опять же, это не идеально, но я не вижу лучшей альтернативы.

Чтобы быть ясным, ваши единственные проблемы с тем, что я предложил (после контраргументов), которые я вижу:

  • Мы не знаем, каким будет поведение var mystring = "string"
  • Вы не хотите, чтобы вам приходилось вводить символы везде.

И мои опасения по поводу директивы:

  • Ненулевые значения не будут явными (но могут встречаться в том же проекте, что и пустые значения). edit : исправлена ​​формулировка, чтобы было больше смысла.
  • Разработчикам будет сложнее отслеживать, допускает ли то, что они пишут, значение NULL или нет.
  • Определения функций, которые вы видите, когда вызываете функцию ( правка : всплывающее окно, предоставляемое Visual Studio, когда вы начинаете вводить функцию), могут иметь или не иметь значение NULL, и вы не сможете сказать.
  • После того, как функция обернута через 2 или 3 слоя, вы не знаете, правильное ли ее определение (поскольку вы не можете использовать вывод типа через файлы, которые не имеют «use nonnull»).

Честно говоря, к реализации «use strict» не стоит стремиться . Он был разработан для JavaScript (а не для TypeScript), а в JavaScript есть несколько драгоценных альтернатив. Мы используем Typescript, поэтому у нас есть возможность сделать что-то лучше.
И я не понимаю, почему эта директива лучше, чем принуждение разработчиков к явному выражению своих намерений (в конце концов, это причина, по которой был создан Typescript, не так ли?).

Изменить: прояснил точку.
Изменить: заключить строку в кавычки

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

Я хочу, чтобы все ненулевые значения были явными (поскольку они могут встречаться в том же проекте, что и пустые значения).

Я хочу, чтобы string на самом деле string | null | undefined не заставляя вас явно это проверять.

Разработчикам будет сложнее отслеживать, допускает ли то, что они пишут, значение NULL или нет.

Я сомневаюсь, что в проекте будет один файл без `` использовать ненулевое значение '', если в проекте используется ненулевое значение, а прокрутка вверху в вашем файле не так уж и сложна (по крайней мере, когда вы пишете файл с менее чем 500 строками кода что в большинстве случаев ...)

Определения функций, которые вы видите при вызове функции, могут иметь или не допускать значения NULL, и вы не сможете определить это.

Да, вы будете, если он исходит из модуля, который имеет директиву 'use non-null', вещь будет напечатана соответствующим образом, если вы просто не рассматриваете все как допускающие значение NULL ...

После того, как функция обернута через 2 или 3 слоя, вы не знаете, правильное ли ее определение (поскольку вы не можете использовать вывод типа через файлы, которые не имеют «use nonnull»).

Я не понимаю твою точку зрения.

Честно говоря, к реализации «use strict» не стоит стремиться. Он был разработан для JavaScript (а не для TypeScript), а в JavaScript есть несколько драгоценных альтернатив. Мы используем Typescript, поэтому у нас есть возможность сделать что-то лучше.
И я не понимаю, почему эта директива лучше, чем принуждение разработчиков к явному выражению своих намерений (в конце концов, это причина, по которой был создан Typescript, не так ли?).

TypeScript - это надмножество javascript, у него есть корень в JavaScript, и цель состоит в том, чтобы позволить вам писать javascript более безопасным способом, поэтому повторное использование концепции JavaScript не кажется необоснованным.

И я не понимаю, почему директива лучше, чем принуждение разработчиков к явному выражению своих намерений.

Поскольку вы просто не сможете достичь того же результата, по всем причинам, которые я привел, наличие типа, не допускающего значения NULL, в системе типов, допускающих значение NULL, - это просто плохая идея.

С моей точки зрения, здесь есть 3 жизнеспособных решения:

  • Критические изменения для 2.0, в которых типы становятся не допускающими значения NULL
  • Флаг компилятора, который по умолчанию переключается на тип, не допускающий значения NULL, как я предлагал несколько десятков комментариев, но @RyanCavanaugh хорошо
  • директива use non-null

Кстати, флаг для меня того стоил бы.

function fn(x: string): number;
function fn(x: number|null): string;

function foo() {
    return fn(null);
}

var x = foo(); // x: number or x: string?

Если функции являются единственной проблемой, исключение может быть сделано в тех случаях, когда перегрузка содержит явный аргумент null - он всегда будет иметь приоритет над неявным null (чтобы быть гармоничным с --noImplicitNull ) . Это также будет интерпретация, которая будет иметь смысл для пользователя (например, «то, что я говорю явно, должно преобладать над тем, что сказано неявно»). Хотя мне интересно, есть ли другие подобные проблемы с флагом, которые нельзя решить таким образом. И, конечно же, это добавляет некоторую хакерскую сложность как спецификации, так и реализации: |

  1. string|null|undefined было явным в моем предложении с использованием флага, поэтому я его не упомянул.
  2. Тогда почему это пофайлово? Я предлагаю свое предложение, потому что оно _не нарушает обратную совместимость_, что важно для меня и, вероятно, многих других разработчиков. И это можно сделать принудительно для всего проекта.
  3. Я использую много файлов, которые не создаю; как я узнаю, что в этом конкретном файле есть "use nonnull"? Если у меня есть 100 файлов, созданных другими людьми, я должен запомнить, какой из этих 100 файлов не нулевой? (или _ каждый раз_ я использую переменную / функцию из другого файла, мне нужно открыть этот файл и проверить его?)
  4. Посмотрим, смогу ли я прояснить это: пример в конце сообщения.
  5. Где остановиться? Где ты это называешь плохим? Одна строка или ключевое слово в начале файла не должны изменять поведение файла. "использовать строгий" был добавлен в javascript, потому что

    1. В то время не существовало большого набора Javascript (например, Typescript), который мог бы делать то, что хотел.

    2. Это была попытка ускорить обработку JavaScript (что, на мой взгляд, единственная причина, по которой это простительно).

      «Использовать строгое» было принято _не потому, что это было правильно_, но потому что это был единственный способ, которым браузеры могли удовлетворить потребности разработчиков. Мне не хотелось бы, чтобы машинописный текст добавлял 1 (затем 2, затем 3, затем 4) другие директивы, которые _фундаментально изменяют способ работы языка_ в виде строк, объявленных в некоторой произвольной области видимости, и влияют на некоторые другие произвольные области. Это действительно плохой языковой дизайн. Я был бы счастлив, если бы "use strict" не существовало в Typescript, а вместо этого было флагом компилятора (и Typescript испускал его в каждый файл / область, в которой он нуждался).

  6. Javascript не является «системой типов, не допускающих значения NULL», Typescript не является «системой типов, не допускающих значения NULL». Введение возможности объявлять типы, не допускающие значения NULL, не означает, что вся система «не допускает значения NULL». Дело не в машинописном тексте.
File 1:
"use notnull"
export string foo() {
    return "mygeneratedstring";
}
File 2:
export string foo() {
    return file1.foo()
}
File 3:
"use notnull"
file2.foo(); //???

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


Кажется, мы спорим по кругу. Если я не понимаю вас, то извините, но вы, кажется, неоднократно поднимаете одни и те же вопросы, и я постоянно на них отвечаю.

@spion
В этом примере строка не является неявным нулем (я считаю, что предполагается, что установлен флаг --noImplicitNull)

string | null | undefined было явно указано в моем предложении с использованием флага, поэтому я оставил его

Я имею в виду, что в реальной системе var myString: string неявно равно var myString: (string | null | undefined); и, кроме того, компилятор не заставляет вас использовать typeguard, если только не все остальные типы объединения.

Тогда почему это пофайлово? Я предлагаю свое предложение, потому что оно не нарушает обратную совместимость, которая важна для меня и, вероятно, многих других разработчиков. И это можно сделать принудительно для всего проекта.
Директива не нарушает обратную совместимость (если вы не использовали строковый литерал 'use not-null' в позиции директивы по довольно странной причине, которую, я уверен, никто не делает). Также в какой-то момент TypeScript придется нарушить обратную совместимость, вот почему semver определил основную версию, но это уже другая история.

Я использую много файлов, которые не создаю; как я узнаю, что в этом конкретном файле есть "use nonnull"? Если у меня есть 100 файлов, созданных другими людьми, я должен запомнить, какой из этих 100 файлов не нулевой? (или каждый раз, когда я использую переменную / функцию из другого файла, мне нужно открыть этот файл и проверить его?)

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

Где остановиться? Где ты это называешь плохим? Одна строка или ключевое слово в начале файла не должны изменять поведение файла. "использовать строгий" был добавлен в javascript, потому что
В то время не существовало большого набора Javascript (например, Typescript), который мог бы делать то, что хотел.
Это была попытка ускорить обработку JavaScript (что, на мой взгляд, единственная причина, по которой это простительно). «Использовать строго» было принято не потому, что это было правильно, а потому, что это был единственный способ, которым браузеры могли удовлетворить потребности разработчиков. Мне не хотелось бы, чтобы машинописный текст добавлял 1 (затем 2, затем 3, затем 4) другие директивы, которые коренным образом меняют способ работы языка в виде строк, объявленных в некоторой произвольной области видимости, и влияют на некоторые другие произвольные области. Это действительно плохой языковой дизайн. Я был бы счастлив, если бы "use strict" не существовало в Typescript, а вместо этого было флагом компилятора (и Typescript испускал его в каждый файл / область, в которой он нуждался).

'use strict' был введен для изменения поведения языка и поддержания ретро-совместимости, точно такой же проблемы, с которой мы сталкиваемся сейчас, и более или менее исчезнет с модулями es6 (и поэтому использование not null может исчезнуть со следующей основной версией машинописного текста) .

Javascript не является «системой типов, не допускающих значения NULL», Typescript не является «системой типов, не допускающих значения NULL». Введение возможности объявлять типы, не допускающие значения NULL, не означает, что вся система «не допускает значения NULL». Дело не в машинописном тексте.

Эти предложения не имеют для меня никакого смысла, JavaScript не имеет системы статических типов, поэтому, как я уже сказал, можно легко сравнить тот факт, что вы можете присвоить null переменной, которая была до string к тому факту, что вы можете присвоить 5 переменной, которая раньше была строкой.
Единственная разница в том, что TypeScript, средство проверки типов, созданное для JavaScript, упорно рассматривает null как присваиваемый всему, а не 5 .

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

Кажется, мы спорим по кругу. Если я не понимаю вас, то извините, но вы, кажется, неоднократно поднимаете одни и те же вопросы, и я постоянно на них отвечаю.

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

@Griffork , кстати, я думаю, что идея string являющегося неявным обнуляемым number | null всегда будет преобладать при первой перегрузке, поэтому в обоих case foo(null) будет string .

@RyanCavanaugh, как ты думаешь, это может сработать?

@fdecampredon

  1. «использовать строгий» был введен для оптимизации javascript. Он должен был использоваться браузерами , поэтому он должен был быть встроен в код. И он должен был быть обратно совместимым, поэтому он должен был быть нефункциональным заявлением.
    Типы, не допускающие значения NULL, не обязательно должны использоваться браузерами, поэтому для них не требуется директива в коде.
  2. Извините, я имел в виду то, что забыл опубликовать, вот оно:

Согласно Facebook : «В JavaScript null неявно преобразуется во все примитивные типы; он также является допустимым обитателем любого типа объекта».
Причина, по которой я хочу, чтобы значение, не допускающее значения NULL, было явным (в каждом действии, которое выполняет разработчик), заключается в том, что, используя не допускающее значение NULL, разработчик подтверждает, что они расходятся с тем, как работает javascript, и что тип, не допускающий значения NULL не гарантируется (так как есть много вещей, которые могут вызвать сбой).
Свойство, не допускающее значения NULL, никогда не может быть удалено.

Я довольно давно использую Typescript. Мне было трудно изначально убедить моего босса перейти с Javascript на Typescript, и мне было довольно сложно убедить его разрешить нам обновиться всякий раз, когда происходило критическое изменение (даже если это не так, это непросто). Когда ES: Harmony поддерживается браузерами, мы, вероятно, захотим его использовать. Если в Typescript произойдет критическое изменение между настоящим моментом и тем, когда Typescript будет поддерживать ES: Harmony (в частности, такое же распространенное, как ненулевое значение), я сомневаюсь, что выиграю этот аргумент. Не говоря уже о количестве времени и денег, которые нам потребуются, чтобы переобучить всех наших разработчиков (я наш «человек с машинописным текстом», моя работа - обучать разработчиков машинописного текста).
Таким образом, я категорически против внесения критических изменений.
Я был бы не против возможности использовать значения, не допускающие значения NULL, если бы существовал способ, которым я мог бы ввести их в будущий код, не нарушая старый код (это причина моего предложения). В идеале в конечном итоге весь наш код будет преобразован, но это будет стоить нам значительно меньше времени и денег на обучение людей и использование сторонних библиотек.

Мне нравится идея использования символов (согласно моему предложению) вместо директивы для каждого файла, потому что они могут распространяться без потери контекста.

@Griffork Хорошо, как я уже сказал, я понимаю вашу озабоченность, надеюсь, вы понимаете мою.
Теперь у меня другое мнение, технологии меняются, нет ничего стабильного (вся драма вокруг angular 2 доказала это), и я предпочитаю ломать вещи и иметь лучшие инструменты, чем придерживаться того, что у меня есть, я действительно думаю, что в в долгосрочной перспективе я выигрываю время и деньги.
Теперь, как мы оба пришли к заключению ранее, я сомневаюсь, что у нас будет консенсус здесь, я предпочитаю директиву, вы предпочитаете свое предложение.
В любом случае, как я уже сказал со своей стороны, я просто попытаюсь форкнуть компилятор и добавить ненулевой тип, так как я думаю, что это не большое изменение в компиляторе.
Спасибо за обсуждение было интересно

@fdecampredon

Спасибо за обсуждение, это было

Предполагая, что вы имели в виду просветительские (интересные работы тоже: D), я тоже получил удовольствие от дискуссии :).
Удачи вам с форком, и я надеюсь, что мы найдем здесь TS-специалиста, который хотя бы отклонит некоторые из наших предложений (чтобы мы знали, что они определенно не хотят реализовывать).

Итак, чтобы подробнее рассказать о предлагаемом мной решении проблемы, описанной @RyanCavanaugh , после добавления нулевого типа и флага --noImplicitNull , проверка типов при запуске без флага будет иметь измененное поведение:

Всякий раз, когда перегрузка типа содержит |null (как в данном примере):

function fn(x: string): number;
function fn(x: number|null): string;

проверка типов примет каждую перегрузку и создаст новые варианты с (неявным) нулевым значением в этой позиции аргумента, добавленным в конец списка:

function fn(x: string): number;
function fn(x: number|null): string;
// implicit signature added at the end, caused by the first overload
function fn(x: null): number;

Это приводит к той же интерпретации, что и для строгой версии --noImplicitNull : при передаче нулевого значения первой совпадающей перегрузкой является явная number|null . Фактически он делает явные нули более сильными, чем неявные.

(Да, я знаю, что количество неявных определений растет экспоненциально с количеством аргументов, которые имеют |null перегрузки - я надеюсь, что есть более быстрая реализация, которая "разрешает" нули на втором проходе, если нет других типов совпадение, но я не думаю, что это будет проблемой в любом случае, поскольку я ожидаю, что такая ситуация будет редкой)

Сформулированное таким образом, я надеюсь, что это изменение будет полностью обратно совместимо с подпиской.

Вот еще несколько идей об остальном поведении:

Назначение null или оставление значений неинициализированными также будет разрешено для значений любого типа, если флаг не передан.

Когда флаг включен, оставление значений неинициализированными (неназначенными) или нулевыми будет разрешено до тех пор, пока они не будут переданы функции или оператору, который ожидает, что они не будут нулевыми. Не уверен насчет учеников: они бы тоже

  • должны быть инициализированы концом конструктора, или
  • инициализацию можно было отследить, проверив, были ли вызваны методы-члены, которые их полностью инициализируют.

Явные пустые союзы будут вести себя так же, как и все другие союзы в обоих режимах. Им потребуется охрана, чтобы сузить тип профсоюзов.

Я просто прочитал _все_ комментарии выше, и это много. Прискорбно, что после стольких комментариев и 5 месяцев мы все еще обсуждаем те же вопросы: синтаксис и флаги ...

Я думаю, что многие люди привели веские аргументы в пользу ценности проверки компилятором статического нуля в больших базах кода. Больше ничего не скажу (кроме +1).

До сих пор основная дискуссия велась о синтаксисе и проблеме совместимости с множеством существующих определений кода TS и библиотек TS. Я понимаю, почему введение нулевого типа и синтаксиса потока ?string кажется наиболее чистым и естественным. Но это меняет значение string и, следовательно, является _ огромным_ критическим изменением для _ всего_ существующего кода.

Предлагаемые выше решения для этого критического изменения заключаются в том, чтобы ввести флаги компилятора ( 'use strict'; был приведен в качестве прецедента. Мы должны повторять их не потому, что TS унаследовал прошлые ошибки JS.

Вот почему я думаю, что аннотация "не нулевая" ( string! , !string , ...) - единственный выход, и я попытаюсь ввести новый аргумент в обсуждение: возможно, это не так плохо.

Никто не хочет иметь челку ( ! ) по всему коду. Но нам, вероятно, и не нужно. TS многое делает о наборе текста.

1. Локальные переменные
Я не ожидаю, что мои локальные переменные будут аннотировать с челкой. Меня не волнует, присваиваю ли я null локальной переменной, если я использую ее безопасным способом, который TS вполне может утверждать.

var x: string;  // Nullable
x.toString();  // Error: x can be undefined or null
x = "jods";
x.toUpperCase();  // Fine.
notNull(x);  // Fine

Довольно часто я использую инициализатор вместо типа.

var x = "jods";  // x: string (nullable)
notNull(x);  // Fine
x = null;  // Fine
notNull(x);  // Error

2. Возвращаемые функции.
Очень часто я не указываю возвращаемое значение. Подобно тому, как TS определяет тип, он может определить, допускает ли он значение NULL или нет.

function() /* : string! */ {
  return "jods";
}

РЕДАКТИРОВАТЬ: плохая идея из-за наследования и переменных функций, см. Комментарий @Griffork ниже
Если я укажу возвращаемый тип, TS прекрасно сможет добавить за меня аннотацию «не нуль».

Если вы хотите переопределить предполагаемую допустимость значений NULL (например, потому что производный класс может возвращать NULL, хотя ваш метод этого не делает), вы указываете тип явно:

function f() : string {  // f is nullable although this implementation never returns null.
  return "abc";
}

3. Параметры функции
Точно так же, как вы должны явно указать тип, это то, где вы _ должны_ указать, если вы не принимаете параметры NULL:

function (x: string!) { return x.toUpperCase(); } // OK
function (x: string) { return x.toUpperCase(); } // Error

Чтобы избежать ошибок компиляции в старых базах кода, нам нужен новый флаг «Null dereference is an error», точно так же, как у нас есть «No implicit any». _Этот флаг не меняет анализ компилятора, только то, что сообщается как ошибка! _
Неужели добавить много челки? Трудно сказать, не делая статистику по реальным, большим кодовым базам. Не забывайте, что необязательные параметры являются обычным явлением в JS, и все они допускают значение NULL, поэтому вам потребуются эти аннотации, если по умолчанию установлено значение «not null».
Эти хлопки также являются приятным напоминанием о том, что нуль не принимается для вызывающих, в соответствии с тем, что они знают о string сегодня.

4. Поля классов
Это похоже на 3. Если вам нужно прочитать поле, не имея возможности сделать вывод о его содержимом, вам нужно пометить его как не допускающее значения NULL при объявлении.

class C {  x: string!; }

function(c: C!) // : string! is inferred
{ return c.x; } // OK, but annotations are required

Мы могли бы представить себе сокращение для инициализатора, возможно:

class C {
  x! = "jods"; // Note the bang: x is inferred as !string rather than just string.
}

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

5. Заявления, .d.ts
Огромное преимущество заключается в том, что все существующие библиотеки работают. Они принимают в качестве входных данных нулевые и ненулевые значения, и предполагается, что они возвращают возможно нулевое значение.
Сначала вам нужно будет явно привести некоторые возвращаемые значения к «not null», но ситуация улучшится, поскольку определения будут медленно обновляться, чтобы указать, когда они никогда не возвращают null. Аннотирование параметров безопаснее, но не требуется для успешной компиляции.

Я думаю, что указание на "ненулевое" состояние значения, в котором компилятор не может сделать вывод, может быть полезно (например, при манипулировании нетипизированным кодом или когда разработчик может сделать некоторые утверждения относительно глобального состояния программы) и сокращенное обозначение может быть неплохо:

var a: { s: string } = something(); // Notice s is nullable.
notNull(a.s); // Error
notNull(<!>a.s);  // OK, this is a shorthand "not-null" cast, because the dev knows about something().

Я думаю, что возможность просто намекнуть компилятору, что что-то не равно нулю, желательна. Это также верно для not-null по умолчанию, хотя сложнее использовать простой логический синтаксис.

Извините, это был очень длинный пост. Я попытался продемонстрировать идею о том, что даже с «нулевым значением по умолчанию» нам не нужно аннотировать весь наш код: TS может сделать вывод о правильности большей части кода без нашей помощи. Двумя исключениями являются поля и параметры. Я думаю, что это не слишком шумно и дает хорошую документацию. Сильной стороной этого подхода является его обратная совместимость.

Я согласен с большей частью того, что вы сказали @ jods4, но у меня есть несколько вопросов и опасений:

2) если компилятор может автоматически преобразовывать обнуляемые в не обнуляемые типы для возвращаемых типов, как вы можете переопределить это (например, для наследования или функций, которые заменяются)?

3 и 5) Если компилятор может преобразовать значение NULL в NULL, тогда сигнатура функции в файле кода теперь может вести себя иначе, чем та же сигнатура функции в файле d.ts.

6) Как работает / выглядит челка при использовании типов объединения?

2) Хороший улов. Я не думаю, что это действительно хорошо работает с наследованием (или «делегатами»).
Мой опыт работы с TS состоит в том, что мы почти никогда не вводим явно возвращаемые значения из функций. TS вычисляет их, и я думаю, что TS может определить нулевую / ненулевую часть.

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

Давайте изменим мое предложение выше: явно типизированное возвращаемое значение _не_ выводится и не допускает значения NULL, если оно не объявлено так. Это хорошо работает в тех случаях, которые вы предлагаете. Он предоставляет способ переопределить вывод компилятора «not null» для контракта, который может иметь значение null в дочерних классах.

3 и 5) С указанным выше изменением это уже не так, верно? Сигнатура функций в коде и определения теперь ведут себя точно так же.

6) Интересный вопрос!
Это действительно не очень хорошо, особенно если вы добавите в смесь дженерики. Единственный выход, который я могу придумать, - это то, что все части должны быть ненулевыми. Возможно, вам понадобится литерал нулевого типа из-за обобщений, см. Мой последний пример.

var x: number | string!; // compiler error
var x: number | string; // x can be null
var x: number! | string!; // x cannot be null
function f<T>() : number | T; // f can be null
function f<T>() : number! | T; // f is nullable if T is nullable
function f<T, G>(): T | G; // f is nullable if T or G is nullable
function f<T>(): T | null; // f is nullable even if T is not nullable.
function f<T>(): T!; // Whatever T is, f never returns null.

// Generics constraint option 1
function f<T!>(x: T!, y: T): T!; // T: not nullable type, x: not-null, y: null, f: not-null
function f<T!>(x: T!, y: T): T;  // T: not nullable type, x: not-null, y: null, f: null

// Generics constraint option 2
function f<T!>(x: T, y: T | null): T; // same as option 1.1
function f<T!>(x: T, y: T | null): T | null;  // same as option 1.2

Для обсуждения обратите внимание, что если вы выбрали ненулевые типы по умолчанию, вам также нужно было бы изобрести новый синтаксис: как выразить, что общий тип не должен допускать значения, допускающие значение NULL?
И, в общем, как бы вы указали, что даже если общий тип T содержит null, функция никогда не возвращает T, но никогда не возвращает null?

Литералы типа допустимы, но тоже не очень удовлетворяют: var x: { name: string }! , особенно если они должны охватывать более одной строки :(

Еще примеры:
Массив ненулевых чисел number![]
Ненулевой массив чисел: number[]!
Ничто не может быть нулевым: number![]!
Дженерики: Array<number!>[]

2, 3 и 5) Я согласен с изменением предложения, похоже, что оно сработает.
6)

function f<T>() : number | T; // f can be null
function f<T>() : number! | T; // f is nullable if T is nullable

Я предполагаю, что здесь вы имеете в виду, что f может возвращать значение null, а не то, что f может быть присвоено значение null.

Что вы думаете о следующем синтаксисе для профсоюзов, как о другом доступном варианте? (выглядит немного глупо, но в некоторых случаях может быть более лаконичным / более понятным).

var x = ! & number | string;

Чтобы обозначить, что ни один из них не допускает значения NULL.


Что касается типовых литералов, я бы сказал, что вы можете нанести удар как на начало буквального выражения, так и на конец, так как var x: !{name: string} , на мой взгляд, будет легче работать, чем иметь его в конце.

@ jods4 Вы читали о моем предложенном исправлении "явный null имеет приоритет над неявным null"? Я думаю, это решает проблему разницы в семантике.

@Griffork
Да, конечно, «имеет значение NULL» было плохой формулировкой для «может возвращать значение NULL».

Это заставляет меня думать, что function f() {} на самом деле является объявлением f , которое может быть установлено позже ... хотим ли мы выразить, что f никогда не будет иметь значение null ? Нравится function f!() {} ? На мой взгляд, это заходит слишком далеко. Я думаю, что проверка типов должна исходить из того, что это всегда так. Есть и другие необычные крайние случаи, с которыми он все равно не справится.

Что касается новой опции, я согласен, что она выглядит немного глупо и вводит один дополнительный символ в объявлении типа: & , с единственной целью ... Мне это не кажется хорошей идеей. И на нескольких языках & AND связывает с более высоким приоритетом, чем | OR чего здесь не происходит.

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

class C {
  name! = "jods";  // Field inferred string, but marked not nullable.
  // Maybe we could do that instead, which works with "!T" convention:
  name : ! = "jods";
  // That kind of make sense with the proposed <!> cast.
}

@spion
Да, я это видел.

Сначала я не уверен, что это действительно решит проблему. Итак, с вашим новым определением, какая перегрузка привязывается к fn(null) ? «Сгенерированный», который возвращает number ? Разве это не противоречит тому факту, что вторая перегрузка также принимает null но возвращает string ? Или это ошибка компилятора, потому что вы не можете выполнить разрешение перегрузок?

Во-вторых, я почти уверен, что мы можем придумать и другие проблемы. Я думаю, что изменение глобального значения исходного кода одним нажатием кнопки просто не сработает. Не только в .d.ts , но люди любят повторно использовать код (повторное использование библиотеки или даже копирование). Вы предполагаете, что семантическое значение моего исходного кода зависит от флага компилятора, и это кажется мне очень ошибочным предложением.

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

@ jods4

В обоих случаях будет использоваться вторая перегрузка.

function fn(x: string): number;
function fn(x: number|null): string; 

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

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

@spion
Так что же случилось с неявным function fn(x: null): number; ? Если вторая перегрузка всегда имеет приоритет, независимо от того, какой флаг используется, все это «исправление» не имеет никакого эффекта?

Другой вопрос: сегодня все определения библиотеки имеют поведение "null allowed". Поэтому, пока они не будут обновлены _все_, я должен использовать флаг «неявно нулевой». Теперь, когда этот флаг включен, как я могу объявить функцию, которая принимает ненулевой параметр в моем проекте?

// null by default flag turned on because of 3rd party libs.
function (x: string)   // <- how do I declare this not null?
{ return x.toUpperCase(); }

Исправление, безусловно, дает эффект. Это делает поведение с флагом и без него согласованным, что устраняет семантическую проблему, упомянутую выше.

Во-вторых, вам не нужно использовать флаг «null по умолчанию» для сторонних библиотек. Самым важным в этой функции является то, что вам не нужно вообще ничего делать, чтобы получить преимущества в подавляющем большинстве случаев.

Допустим, есть библиотека, которая вычисляет количество слов для строки. Его декларация

declare function wordCount(s: string): number;

Допустим, ваш код использует эту функцию следующим образом:

function sumWordcounts(s1:string, s2:string) {
  return wordCount(s1) + wordCount(s2);
}

Эта программа проходит через оба компилятора: даже если неявные значения NULL отключены. Код полностью обратно совместим.

Почему? Потому что компилятор в его нынешнем виде уже верит, что значения не равны нулю везде, где вы пытаетесь их использовать (даже если они теоретически могут быть нулевыми).

Новый компилятор также предположит, что значения не равны нулю, когда вы попытаетесь их использовать. У него нет оснований полагать иначе, поскольку вы не указали, что значение может быть нулевым, поэтому часть поведения остается прежней. Изменение поведения вступает в силу только при присвоении значения null (или оставлении переменных неинициализированными) и последующей попытке передать эти значения функции, подобной приведенной выше. Они не требуют никаких изменений в других частях кода, которые обычно используют значения.

Теперь давайте посмотрим на реализацию wordCount

function wordCount(s) {
  if (s == '') return null;
  return s.split(' ').length
}

К сожалению, этот тип не рассказывает всей истории. Эта функция может вернуть нулевое значение.

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

После изменения noImplicitNull мы получим здесь точно такой же результат, если будем использовать те же определения типов. Код скомпилируется без нареканий. Компилятор по-прежнему с радостью считает, что wordCount всегда возвращает число. Программа все равно может выйти из строя, если мы передадим пустые строки, как и раньше (старый компилятор не предупредит нас, что возвращаемое число может быть нулевым, и новый компилятор тоже не будет, поскольку он доверяет определению типа). И если мы хотим такого же поведения, мы можем сохранить его, ничего не меняя в нашем коде. (1)

Однако теперь мы сможем добиться большего: мы сможем написать улучшенное определение типа для wordCount:

declare function wordCount(s:string):string|null;

и получаем предупреждение компилятора всякий раз, когда мы пытаемся использовать wordCount не проверяя возвращаемое значение null.

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


(1): здесь уже есть улучшение, даже без улучшения определения типа. с новыми определениями типов вы не сможете случайно передать null в sumWordcounts и получить исключение нулевого указателя, когда функция wordCount попытается .split() этот null.

sumWordcounts(null, 'a'); // error after the change

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

С необязательными аргументами все будет хорошо:

declare function f(a: string, b?:string); 

но не для аргументов, которые не являются необязательными и могут быть нулевыми

declare function f(a: string, b:string); // a can actually be null

Я бы сказал, что эти функции довольно редки, и требуемые исправления будут минимальными.

@spion

Исправление, безусловно, дает эффект. Это делает поведение с флагом и без него согласованным.

Каким образом вы можете привести полный пример? Вы также сказали:

В обоих случаях будет использоваться вторая перегрузка.

Что, на мой взгляд, означает, что исправление не действует?

Эта программа проходит через оба компилятора: даже если неявные значения NULL отключены. Код полностью обратно совместим.

Да, он компилируется без ошибок в обоих случаях, но не с одинаковым результатом. sumWordCounts() будет набираться как number! в одном случае и number? во втором. Изменение семантики кода с помощью переключателя крайне нежелательно. Как было продемонстрировано ранее, это изменение может иметь глобальные последствия, например, для разрешения перегрузок.

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

Нет! В этом весь смысл: я хочу, чтобы компилятор выдавал мне ошибку, когда я использую потенциально нулевое значение. Если он «предполагает» ненулевое значение, как это делаю я при кодировании, то эта функция бесполезна!

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

Не уверен, что понимаю, поскольку «функция, подобная приведенной выше», на самом деле принимает нули ...

Читая конец вашего последнего комментария, у меня сложилось впечатление, что мы не получим настоящего статического анализа NULL, пока не включим переключатель, чего вы не сможете сделать, пока _все_ определения вашей библиотеки не будут исправлены. :(

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

Вот пример и то, как я бы хотел, чтобы компилятор вёл себя:

Предположим, что есть библиотека с функциями yes(): string! которая никогда не возвращает null, и no(): string? которая может возвращать null.

Определение .d.ts :

declare function yes(): string;
declare function no(): string;

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

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

function x(s: string!) {  // inferred : string, could be explicit if we want to
  return s.length === 0 ? null : s;  // no error here as s is declared not null
}

x(no());  // error: x called with a possibly null parameter
y(<!>yes());  // no error because of not null cast. When .d.ts is updated the cast can be dropped.

Как это будет работать с вашей идеей?

Эта ветка представляет собой слишком длинное обсуждение (130 комментариев!), Что затрудняет отслеживание идей, предложений и проблем, упоминаемых всеми.

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

@spion, поскольку ваша идея включает флаги компилятора, для каждого фрагмента кода вы должны задокументировать поведение TS с установленным и не установленным флагом.

Вот суть маркера !T not-null:
https://gist.github.com/jods4/cb31547f972f8c6bbc8b

Это в основном то же самое, что и в моем комментарии выше, со следующими отличиями:

  • Я заметил, что "ненулевой" аспект параметров функции может быть безопасно выведен компилятором (спасибо @spion за это понимание).
  • Я включил комментарии @Griffork , в частности о выводе возвращаемых значений, и попробовал !T вместо T! .
  • Я добавил несколько разделов, например, о поведении конструктора.

Не стесняйтесь комментировать, разветвлять и создавать альтернативные предложения ( ?T ).
Попробуем продвинуться вперед.

@ jods4

В обоих случаях будет использоваться вторая перегрузка.

Что, на мой взгляд, означает, что исправление не действует?

  1. Это означает, что разрешение перегрузки изменено, чтобы семантика языка была согласованной для обоих флагов.

Да, он компилируется без ошибок в обоих случаях, но не с одинаковым результатом. sumWordCounts () будет набрано как число! в одном корпусе и в количестве? во-вторых. Менять семантику кода переключателем крайне нежелательно. Как было продемонстрировано ранее, это изменение может иметь глобальные последствия, например, для разрешения перегрузок.

  1. В моем предложении нет number! . В обоих случаях типом будет просто number . Это означает, что разрешение перегрузки продолжает работать как обычно, за исключением значений NULL, и в этом случае новое поведение для явных значений NULL, имеющих приоритет над неявным, нормализует семантику с обратной совместимостью.

Нет! В этом весь смысл: я хочу, чтобы компилятор выдавал мне ошибку, когда я использую потенциально нулевое значение. Если он «предполагает» ненулевое значение, как это делаю я при кодировании, то эта функция бесполезна!

  1. То, что я пытался выразить, заключается в том, что обратная несовместимость функции очень мала (с точки зрения объявлений типов). Если вы не используете его, вы получите такое же поведение, как и раньше. Если вы хотите использовать его, чтобы выразить возможность того, что может быть возвращено значение нулевого типа, теперь вы можете это сделать.

Это как если бы компилятор раньше использовал тип string для значений типа object и string , разрешая все строковые методы для всех объектов и никогда не проверяя их. Теперь у него будет отдельный тип для Object , и вы можете начать использовать этот тип вместо этого, чтобы обозначить, что строковые методы не всегда доступны.

Не уверен, что понимаю, поскольку «функция, подобная приведенной выше», на самом деле принимает нули ...

Давайте посмотрим на эту функцию:

function sumWordcounts(s1:string, s2:string) {
  return wordCount(s1) + wordCount(s2);
}

Под новым флагом компилятора вы не сможете вызывать его с нулевыми значениями, например, sumWordCounts(null, null); и это единственная разница. Сама функция будет компилироваться, потому что определение wordCounts говорит, что она принимает строку и возвращает число.

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

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

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


Итак, существующие определения типов

declare function yes(): string;
declare function no(): string;

Допустим, ваш код был написан до того, как эта функция была добавлена:

function x(s: string) {
  return s.length === 0 ? null : s;
}

В новом компиляторе поведение будет точно таким же, как и у старого.

x(no());  // no error
x(yes());  // no error

Если вы не попробуете что-то, что, как известно компилятору, может быть нулевым, например, результат x ()

x(x(no())) // error, x(something) may be null

Вы смотрите на no() и можете решить, что случаи, когда он возвращает null, редки, поэтому вы не собираетесь моделировать это, или вы можете исправить определение типа.

Теперь давайте посмотрим, что происходит с вашим предложением: каждая строка кода, даже касающаяся внешней библиотеки, ломается. Функции, которые имели совершенно допустимые определения типа, как указано выше, также ломаются. Вам нужно везде обновить аннотацию каждого аргумента и добавить ! чтобы компилятор перестал жаловаться, или везде добавить нулевые проверки.

Суть в том, что система типов в TypeScript в настоящее время неверна. Он имеет двойную интерпретацию нулевых значений:

Когда мы пытаемся присвоить значение null некоторой переменной типа T или передать значение null в качестве аргумента, когда требуется тип T , он действует так, как если бы этот тип был T|null .

Когда мы пытаемся использовать значение типа T , оно действует так, как если бы тип был T и нет возможности обнулить.

В вашем предложении предлагается всегда рассматривать все нормальные типы T как T|null ; мой предлагает относиться к ним просто как к T . Оба меняют семантику. Оба заставляют компилятор вести себя "правильно"

Мой аргумент состоит в том, что вариант «просто T » гораздо менее болезнен, чем кажется, и созвучен подавляющему большинству кода.

edit: Я только что понял, что ваше предложение может заключаться в том, чтобы продолжать рассматривать типы без "!" или "?" так же, как и раньше. Да, это обратно совместимо, но нужно много работать, чтобы получить какие-либо преимущества (поскольку подавляющее большинство кода просто не имеет дело с нулевыми значениями, кроме проверки / выброса.

@spion

1. Это означает, что разрешение перегрузки изменено, чтобы семантика языка была согласованной для обоих флагов.

Вы просто констатируете один и тот же факт, но мне до сих пор неясно, в каком случае он обращается и каким образом.
Вот почему я попросил конкретный пример.

1. В моем предложении нет number! . В обоих случаях типом будет просто number .

Это неверно, я знаю, что синтаксис отличается, но основные концепции те же. Когда я говорю number! я подчеркиваю ненулевой числовой тип, который в вашем предложении будет просто number . И во втором случае в вашем случае будет не number , а number | null .

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

Я думаю, что описания, подобные этому, содержат массу предположений и сокращений. Вот почему я думаю, что для достижения прогресса нам нужно перейти к более конкретным примерам с кодом, синтаксисом и объяснениями того, как работает система. Ты говоришь:

Подавляющее большинство кода просто не имеет дело с нулевыми или неопределенными значениями, кроме проверки, являются ли они нулевыми / неопределенными, и выдачи ошибки.

Весьма спорно.

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

Еще более спорно. Библиотеки JS полны таких примеров.

, которые узнаваемы и, вероятно, могут быть смоделированы соответствующим образом с помощью нового переключателя

Сделайте более конкретное предложение, потому что это огромный ярлык. Думаю, я могу понять, к чему ведет настоящий JS-код, но как «распознать» необязательный аргумент внутри declare function(x: {}); или внутри interface ?

Не так часто функции возвращают нулевые значения, как в моем примере.

Снова очень спорно. Многие функции возвращают значение null или undefined : find (когда элемент не найден), getError (когда нет ошибки) и т. Д. ... Если вам нужно больше примеров, просто посмотрите на стандартные API-интерфейсы браузера, вы найдете _plenty_.

Я говорю о том, что подавляющее большинство определений типов останутся верными.

Как вы можете понять из моих предыдущих комментариев, я не уверен в этом.

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

На данный момент это утверждение мне не кажется тривиальным. Вы можете привести конкретные примеры того, как это работает? Особенно часть _progressively_.

Теперь давайте посмотрим, что происходит с вашим предложением: каждая строка кода, даже касающаяся внешней библиотеки, ломается. Функции, которые имели совершенно допустимые определения типа, как указано выше, также ломаются. Вы должны обновить каждую аннотацию аргумента повсюду и добавить ! чтобы компилятор перестал жаловаться, или везде добавить нулевые проверки.

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

Предположим, вы берете существующую базу кода, в которой используются определения библиотек, и пытаетесь скомпилировать ее с моим предложением, не меняя код:

  • Вы получите много преимуществ нулевого анализа даже без аннотаций (почти так же, как вы получаете это в компиляторе Flow).
  • Единственное, что сломается: использование возвращаемого значения библиотечной функции, которая, как вы знаете, не возвращает null.
declare function f(): string; // existing declaration, but f will never return null.
var x = f();
x.toUpperCase();  // error, possibly null reference.

Долгосрочное решение - обновить определения библиотек, чтобы они были более точными: declare function f(): !string;
Кратковременное исправление - добавить ненулевое приведение: var x = <!>f(); .
А чтобы облегчить обновление больших проектов с большим количеством зависимостей, я предлагаю добавить флаг компилятора, подобный «без неявных любых»: «обрабатывать возможные пустые ссылки как предупреждение». Это означает, что вы можете использовать новый компилятор и ждать, пока библиотеки обновят определение. Примечание: в отличие от вашего предложения этот флаг не меняет семантику компилятора. Он изменяет только то, что сообщается как ошибка.

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

В этом выпуске с более чем 130 комментариями слишком много дискуссий о разных вещах.

Предлагаю продолжить здесь _общее_ обсуждение.

Для обсуждения конкретных предложений, которые могут реализовать ненулевые типы в TS, я предлагаю открыть новые вопросы, каждый из которых касается одного проектного предложения. Я создал # 1800, чтобы обсудить синтаксис !T .

@spion Я предлагаю вам также создать проблему для вашего дизайна.

Многим нужны ненулевые типы. Это легко на новых языках (например, Rust), но очень сложно модернизировать существующие языки (люди давно просят ненулевые ссылки в .net - и я не думаю, что мы когда-нибудь получим их). Просмотр комментариев здесь показывает, что это нетривиальная проблема.
Flow убедил меня, что это можно сделать для TS, давайте попробуем сделать это!

@ jods4 Мне жаль это говорить, но ваше предложение не имеет ничего общего с ненулевым типом, оно больше связано с каким-то анализом потока управления, трудно предсказуемым и полностью нарушающим ретро-совместимость (фактически, большая часть кода, который действительно 1.4 ts выйдет из строя по правилу, описанному в вашей сути).
Я согласен с тем, что обсуждение ни к чему не приведет, и что 130 комментариев +, возможно, слишком много. Но, возможно, это потому, что здесь обсуждают две группы людей:

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

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

@fdecampredon
Что касается моей сути, я скопировал ваши аргументы в # 1800, и если вы искренне заинтересованы, мы можем обсудить, почему там плохой дизайн. Я не совсем понимаю вашу точку зрения, но не хочу здесь начинать обсуждать, поэтому я и создал # 1800 в первую очередь.

Что касается этой ветки, я согласен с вами, что обсуждение никуда не денется ...

Как вы можете понять из моих предыдущих комментариев, я не уверен в этом.

Что ж, я действительно не знаю, как это объяснить по-другому - я уже объяснял это несколько раз. Позвольте мне попробовать еще раз.

Прямо сейчас вы не можете выразить это

declare function err():string;

возвращает null. Это невозможно, потому что машинописный текст всегда с радостью позволит вам сделать err().split(' ') . Вы не можете сказать «это может быть неверно».

После этого изменения вы сможете, изменив string на ?string или string|null

Но если вы этого не сделаете, _вы не потеряете ничего_, что было до изменения:

У вас не было нулевой проверки перед изменением и не было после (если вы ничего не делаете). Я утверждал, что это в основном обратная совместимость. Конечно, это еще предстоит продемонстрировать на более крупных кодовых базах.

Разница в том, что вы не могли принудительно выполнить нулевую проверку до изменения, но вы _ можете_ после (постепенно, сначала в определениях, которые вас больше всего волнуют, а затем в других местах)

@fdecampredon Я все еще обсуждаю эту тему, потому что считаю, что существует множество неправильных представлений о проблеме и о том, насколько «сложно» и «строго» будет использовать эту функцию. Я подозреваю, что многие потенциальные проблемы сильно преувеличены. Мы, вероятно, должны попробовать реализовать базовый вариант --noImplicitNull в вилке - я не уверен, что смогу найти время в следующем периоде, но если я это сделаю, я попробую , звучит как забавный проект.

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

И @fdecampredon, моя единственная просьба заключалась в том, чтобы не было большого

Я предполагаю, что @spion в вашем текущем примере вы могли бы в значительной степени продолжить кодирование Typescript так же, как мы делаем сейчас, если вы предположите, что локальная переменная, если она не назначена, преобразуется в обнуляемые параметры, а параметры функции с помощью? также может быть нулевым. Ну, за исключением того примера, который у вас был ранее с параметрами ранней функции, допускающими значение NULL.

@spion
То, что я понял из вашего последнего комментария, сильно отличается от того, что я понял раньше ...

Итак, : string - это «ваш старый строковый тип, который никогда не будет проверяться на пустоту, например <any> никогда не проверяется на правильность типа».

Новый синтаксис : string | null обратно совместим, потому что он не существовал раньше, и доступ к этому типу без проверки на null является ошибкой.

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

Первый вопрос, который приходит в голову, - как установить ограничения на входные значения (например, параметры функции):

declare f(x: string): void;
f(null);

Это нормально или это ошибка? Если все в порядке, то как я могу сделать ошибку.

_Я все еще думаю, что какая-то идея внутри этого обсуждения потеряна, разве вы не хотите открыть новый выпуск с этой вашей идеей? Мы могли бы обсудить это там.

@jods, что было бы ошибкой:

declare f(x: string): void; //string must not be null.
declare f(x: string|null): void; //string may be null (not sure about undefined here).
declare f(x?: string): void; //I assume x may be null or undefined.

@ jods4 Я не думаю, что нам нужно создавать несколько вопросов по одной теме, тогда все станет труднее отслеживать, так как вам придется перейти к 10 различным предложениям, чтобы посмотреть, не произошло ли что-нибудь / подписаться на 10 разных . И все они должны будут иметь ссылки на другие предложения во вступительных предложениях, чтобы каждый, кто ищет недопустимость нулевого значения, не только видел и голосовал за одно.

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

@Griffork
Но только после того, как вы включите волшебный флаг, верно?
Таким образом, со снятым флагом у вас есть непроверенный код, за исключением новых типов, допускающих значения NULL, которых раньше не было; тогда, когда вы включаете флаг, все типы не допускают значения NULL и проверяются?

Я думаю, что конечный результат, наверное, лучшее решение. _Но он нарушает практически весь код, который существует сегодня. _ Представьте, что у меня есть 300 КБ строк кода ... Мне нужно добавить аннотацию, допускающую значение NULL, везде, где тип действительно допускает значение NULL, прежде чем я смогу включить флаг. Это большое дело.

Если я правильно понимаю, это хорошо для новых проектов (после обновления .d.ts ), но очень болезненно для существующего кода.

@Griffork, моя проблема с этой единственной проблемой и длинной

В первоначальном предложении

Да, но тот, кто участвует в этом обсуждении, должен прочитать все, что было до него. Если вы думаете, что люди не должны читать все, что было написано ранее (и, следовательно, потенциально предлагать те же предложения), тогда мы можем разделить это; но я не согласен. Смысл этого в том, что, хотя мы говорили о разных деталях или различных возможных реализациях, мы все говорили об одном и том же .
Одна и та же тема, а не разные темы.
Все разговоры централизованы, и никто из них не может похоронить, если на них не ответят.

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

Разделение предложений друг от друга означает, что, если кто-то обнаруживает недостаток, он должен обратиться к нескольким разным потокам и написать это. Тогда люди не учатся на ошибках друг друга, и у людей есть способность (которая у них будет) уединяться в одном обсуждении и повторять ошибки, описанные в других обсуждениях.

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

Также @ jods4 это только

Imo гораздо меньше критических изменений, чем ваше предложение, в котором каждое определение, которое не должно иметь null, должно быть проверено / затронуто, прежде чем вы сможете использовать их и получить преимущества типов, не допускающих значения NULL.

Вот пример того, как может выглядеть процесс «обновления» предложений @ jods4 и @spion (при условии, что все необходимые флаги включены):

function a(arg1: string): string;
a("mystr").toLowerCase(); //errors in <strong i="11">@jods4</strong>'s proposal because a may return null.
a("mystr").toLowerCase(); //fine in <strong i="12">@spion</strong>'s proposal.

a(null).toLowerCase(); //fine in <strong i="13">@jods4</strong>'s proposal because a may accept null.
a(null).toLowerCase(); //errors in <strong i="14">@spion</strong>'s proposal since a doesn't accept null.

В конце концов, они оба ломают изменения. Их обоих так же болезненно отлаживать, поскольку ошибки возникают при использовании переменной вместо объявления переменной. Разница в том, что одна ошибка возникает, когда значение null присваивается чему-то (@spion), а другие ошибки, когда значение null чему-либо не присваивается (@ jods4). Лично я присваиваю null вещам гораздо реже, чем когда я не присваиваю null вещам, поэтому @spion - меньшее изменение для меня, с которым я работаю.

Еще мне нравятся:

  • Типы переменных у меня динамически не меняются. Если бы я хотел этого, я бы использовал поток:
var s: string;
s.toUpperCase(); // error
var t = (s || "test").toUpperCase(); // ok. Inferred t: string
yes(t);  // ok
t = no();
yes(t);  // error

Лично я бы хотел получить ошибку, если бы вызвал no (), а не неявное изменение типа.

  • Nullables, содержащие слово null, а не какой-либо символ, легче читать и меньше запоминать. Действительно ! следует использовать для «не», а не «не-нуль». Я ненавижу вспоминать, что делает символ, точно так же, как я ненавижу аббревиатуры, я никогда не могу их запомнить, и они значительно замедляют мою скорость работы.

@ jods4 Я знаю, что людям сложно наверстать упущенное в текущем разговоре, но разделение его на отдельные вопросы не поможет, поскольку в конечном итоге нам придется поднять все эти вопросы во всех из них, потому что несведущие зададут те же вопросы, что и мы. Это действительно не поможет в разговоре, просто будет сложнее поддерживать и не отставать.

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

Это критическое изменение каждый раз, когда у вас есть что-то, допускающее значение NULL (что подразумевает, что в какой-то момент ему присваивается значение NULL, иначе оно не было бы действительно допускающим значение NULL). Об ошибке будет сообщено, если функции присваивается / передается значение null, но исправление происходит при объявлении. Это включает в себя объявления функций, объявления переменных, объявления полей в классах ... Многое может быть потенциально проверено и изменено.

@ jods4 должны были быть

@spion Я думаю, что у меня есть более простое решение проблемы @RyanCavanaugh с флагом '-nonImplicitNull', идея довольно проста, нам просто нужно запретить ?type type|null или type|undefined без этого флага.

@fdecampredon, тогда вам понадобятся две версии каждой доступной библиотеки, которые должны постоянно
Это также убило бы наш процесс, в котором у нас есть один нестрогий проект для тестирования кода и запуска тестов, который зависит от кода более строгого проекта.

@ jods4

declare f(x: string): void;
f(null);

Да, при использовании предложенного флага --noImplicitNull это будет ошибкой.

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

Я заметил, что предложение !T также нарушит работу большого количества кода. Не потому, что изменилась семантика существующих типов, а потому, что новый анализ вызовет ошибки во многих местах.

Я закрыл его. Если мы сломаем много кода, я предпочитаю другой подход, он кажется более чистым.

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

Интересно, какова позиция официальной команды TS относительно разрывающего аспекта этого по сравнению с его преимуществами.

Маловероятно, что мы когда-либо сделаем такой большой перерыв, как «сделать все не допускающим значения NULL по умолчанию». Если бы было какое-то предложение, которое _выпускало бы новые ошибки только в тех случаях, которые явно являются ошибками, это могло бы быть на столе, но такие предложения трудно найти: wink:

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

: +1: обнаружен здравомыслящий человек: @RyanCavanaugh

Как насчет того, чтобы не вносить никаких изменений в существующий способ работы, но ввести нулевой тип для объединений, который заставляет вас охранять переменную перед ее использованием (или назначать ее переменной с ненулевым типом)? Поведение по умолчанию по-прежнему позволяет присваивать значения NULL как переменным с обычным типом, так и переменным с типом NULL, однако функция, которая может возвращать значение NULL при правильной разметке, вызовет приведение.

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

Затем (возможно, позже?) Вы можете добавить флаг --noImplicitNullCast, который предотвращает присвоение значения null переменным, которые не имеют значения null как часть их типизации (это более или менее работает как предложение @spion с включенным флагом).

Я полагаю, что это будет не слишком отличаться от обычного набора текста и добавления флага --noImplicitAny.

@Griffork
Первая половина пакета (добавить тип null и запретить разыменование без защиты) несовместима на 100%. Вы нарушите больше кода, чем думаете. Учти это:

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

interface Array<T> {
  find(predicate: (T) => bool) : T | null;
  pop() : T | null;
}
interface Storage {
  getItem(key: string) : any | null;
}

Таких API много: find возвращает undefined если ни один элемент массива не соответствует предикату, pop возвращает undefined если массив пуст, localStorage.getItem или sessionStorage.getItem оба возвращают null если ключ не был найден.

Следующий код является легитимным и ранее был скомпилирован безупречно. Теперь он выйдет из строя с ошибкой:

var xs: string[];
if (xs.length > 0) return xs.pop().trim();  // error xs.pop() may be undefined (false positive)

var items : { id: number }[];
var selectedId : number;
// Assume we are sure selectedId is amongst items
var selectedItem = items.find(x => x.id === selectedId);
selectedItem.toString(); // error selectedItem may be undefined (false positive)

Та же идея, если вы берете что-то из localStorage которое, как вы знаете, есть. Вокруг много такого кода, и теперь для его компиляции требуется приведение (или какой-то новый синтаксис), и он сообщает компилятору: предположим, что это не null, я знаю, что делаю.

Конечно, это будет включать в себя несколько реальных ошибок. Но он также будет включать в себя множество ложных срабатываний, и это критические изменения. В 100K + LOC это нетривиальное обновление компилятора.

Это, вероятно, означает, что единственный подход, который будет работать: новый код, написанный с нуля, или перенесенный старый код в полной мере используют нулевую проверку; у устаревшего кода нет никакой пользы. Это управляется параметром компилятора (--enableNullAnalysis) и без «переходного» или «прогрессивного» пути миграции.

Ну да. Но вы всегда можете заморозить lib.d.ts для проекта или взять старую.
Новый набор текста сломает старый код, это все равно произойдет. Практически вся новая типизация, когда она вводится, требует, чтобы старый код тем или иным образом обновлялся для правильной работы (например, объединения, дженерики).
Это изменение означает, что те, кто хочет игнорировать обновление или заморозить определения своей библиотеки / кода, не пострадают.
Теоретически большая часть кода, использующего эти функции, в любом случае должна быть защищена (и, как правило, находится в больших базах кода), потому что в вашем коде скрываются ошибки, если вы этого не сделаете. Таким образом, это изменение скорее приведет к обнаружению ошибок, чем к повсеместным критическим изменениям.

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

Редактировать 2
Если вы не нарушаете _some_ (неверный / небезопасный) код, то нет никакого смысла делать какие - либо изменения , чтобы сделать эту тему когда -

Но опять же, поскольку это всего лишь синтаксическая проблема, все будет по-прежнему правильно компилироваться, это просто повсюду ошибка.

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

Это не тот случай. За исключением исключительных обстоятельств, мы не будем добавлять новые функции, нарушающие существующий код. Обобщения и объединения не были добавлены в lib.d.ts таким образом, чтобы потребителям lib.d.ts требовалось обновить свою кодовую базу, чтобы использовать новую версию компилятора. Да, люди могут выбрать более старую версию библиотеки, но это не тот случай, когда мы собираемся вносить изменения в язык, которые нарушают работу большого количества существующего кода (как в случае практически всех основных языков). Из этого правила будут редкие исключения (https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes), но их будет немного, и чаще всего, если мы считаем, что код, который мы нарушаем, может быть Жук.

Но вы всегда можете заморозить lib.d.ts для проекта или взять старую

Замораживание lib.d.ts (и любых других .d.ts, которые я могу использовать, кстати) имеет очень серьезные последствия. Это означает, что я не могу получать обновления используемых мной библиотек или новых API HTML. К этому нельзя относиться легкомысленно.

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

JS имеет традицию возвращать undefined (или иногда null ) во многих случаях, когда возникает ошибка на других языках. Примером этого является Array.prototype.pop . В C # вызов из пустого стека вызовет выброс. Таким образом, можно сказать, что он всегда возвращает допустимое значение. В Javascript всплывающий пустой массив возвращает undefined . Если ваша система типов строго придерживается этого правила, вы должны как-то об этом позаботиться.

Я знал, что вы собираетесь ответить на этот вопрос, поэтому я написал примеры, которые представляют собой законный рабочий код. В больших базах кода вы найдете множество примеров, когда api может возвращать null в некоторых ситуациях, но вы знаете, что это безопасно в вашем конкретном случае (и поэтому вы игнорируете любые проверки безопасности).

Я просматривал часть микрозадач в какой-то библиотеке всего час назад. Основная часть в основном сводится к следующему:

class MicroTasks {
  queue: Array<() => void>;

  flushQueue() {
    while (queue.length > 0) {
      let task = queue.pop();
      task();  // error possible null dereference (not!)
     }
  }
}

Таких случаев много.

Что касается вашего Edit 2: да, надеюсь, это изменение _ отловит ошибки и укажет на недопустимый код. Я убежден, что это полезно, и поэтому я бы хотел, чтобы это как-то произошло. Но команда TS рассмотрит критические изменения в _valid_ коде. Здесь необходимо найти компромиссы.

@danquirk Справедливо, тогда я полагаю, что эта функция никогда не будет реализована.
@ jods4 Я понимаю, о чем вы говорите, но пустые значения никогда не могут быть реализованы без нарушения этого кода.

Печально, я должен наконец закрыть этот вопрос?

Наверное.

Я думаю, если мы будем избегать аргументов, допускающих и не допускающие значения NULL ...

Проблема может быть решена (или смягчена) ненавязчивым способом с помощью набора новых функций:

  • Введите символ null-typeguard ?<type>
    Если тип помечен этим символом, прямой доступ к нему является ошибкой.

`` TypeScript
var foo:? строка;

foo.indexOf ('s'); // Ошибка
foo && foo.indexOf ('s'); // Хорошо
`` ''

  • Ввести флаг --nouseofunassignedlocalvar

`` TypeScript
вар foo: строка;

foo.indexOf ('s'); // ошибка

foo = 'бар';

foo.indexOf ('s'); // Ладно
`` ''

Интересное историческое примечание : пытаясь найти связанную проблему для этого, я столкнулся со старой проблемой на codeplex, в которой упоминается параметр компилятора --cflowu еще в феврале 2013 года. @RyanCavanaugh , интересно, что случилось с этим флагом?

  • Познакомьтесь с оператором безопасной навигации №16.

TypeScript var x = { y: { z: null, q: undefined } }; console.log(x?.y?.z?.foo); // Should print 'null'

В сочетании эти функции помогут отловить больше ошибок, связанных с использованием null, не вызывая у всех нервного срыва.

@NoelAbrahams :
Ваше первое предложение, по сути, точно такое же, как и мое последнее, вы просто используете? вместо | null (прочтите сообщение @ jods4 о проблемах с обновлением lib.d.ts и критических изменениях).

У вашего второго предложения есть проблема, которую ранее решал @RyanCavanaugh (см. Выше):

Флаги, изменяющие семантику языка, опасны. Одна из проблем заключается в том, что эффекты потенциально очень нелокальные:
... [отрывок] ...
Единственный безопасный способ сделать это - сохранить семантику присваиваемости одинаковой и изменить то, что является ошибкой, а не то, что не зависит от флага, подобно тому, как сегодня работает noImplicitAny.

Я почти уверен, что флаг, похожий на другой, был предложен ранее, а затем от него отказались из-за комментария @RyanCavanaugh .

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

Можно ли реализовать одно из предложенных предложений и просто не обновлять lib.d.ts для использования значений NULL (и других библиотек)? Тогда сообщество может поддерживать / использовать свои собственные версии с значениями NULL, если они хотят?

Редактировать:
В частности, все библиотеки содержат самую свежую информацию, но не обновляют их типизацию, чтобы требовать типографские защиты.

@RyanCavanaugh Я вернусь и

Определения типов

В любом случае, похоже, эта проблема безнадежная.

Между прочим, был модифицированный компилятор от MSR, который, помимо прочего, делал это - есть ли какие-нибудь документы с их результатами? Изменить: нашел: http://research.microsoft.com/apps/pubs/?id=224900, но, к сожалению, я не уверен, что это связано.

@Griffork , да, я уверен, что почти все обсуждалось в той или иной форме - учитывая длительность обсуждения. Мое резюме является практическим о смягчении проблем, связанных с null, путем введения ряда связанных функций.

не добавлять везде новые нулевые или неопределенные проверки

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

Я также думаю, что вероятность того, что это попадет в TS, мала ... :(

Единственное решение, которое я могу придумать, - это сделать нулевую безопасность флагом компилятора выбора. Например, введите новый синтаксис T | null или ?T но не вызывайте _любую_ ошибку, если проект не согласен. Таким образом, преимущества получают новые проекты, а также старые проекты, которые решили сделать свой код совместимым с новой функцией. Большие кодовые базы, которые слишком велики, чтобы их можно было легко адаптировать, просто не имеют этой функции.

Тем не менее, даже в этом случае остается несколько проблем, чтобы заставить эту функцию летать ...

@NoelAbrahams, извините, моя точка зрения заключалась не в том, что это плохое предложение, а в том, что это не тот ответ, который требовался от начала этого обсуждения.
Я уверен, что буду использовать эту функцию, когда (/ если) она станет родной, звучит действительно хорошо.

@ jods4 , у которого такая же проблема с комментарием @RyanCavanaugh, который я цитировал выше (ошибка присваивания из-за проблемы в другом месте при изменении флага).

Опять же, самый простой способ - реализовать одно из других предложений (возможно, @spion ) и не добавлять новые типы в .d.ts '.

@Griffork
Не обязательно, хотя это одна из «нескольких оставшихся проблем». Эта функция должна быть спроектирована так, чтобы включение флага _не_ изменяло семантику программы. Это должно вызывать только ошибки.

Например, если мы выберем 'not null' !T design, я не думаю, что у нас есть эта проблема. Но это далеко не решающий вопрос.

Я также должен отметить, что хотя T | null является критическим изменением, ?T - нет.

@ jods4 У меня создалось впечатление, что «изменение семантики» включает «изменение возможности назначения», в любом случае мои (и, возможно, @RyanCavanaugh ) опасения по поводу нелокальных эффектов все еще остаются в

@NoelAbrahams как?
Т | null требует typeguard,? T требует typeguard, это одно и то же с разными именами / символами.
Да, вы можете включить их обоих с помощью флажка.
Я не вижу разницы?

@Griffork , как я понимаю, ?T просто означает «не осуществлять доступ без проверки на нуль». Можно бесплатно добавить эту аннотацию к новому коду, чтобы принудительно выполнить проверку. Я считаю это своего рода оператором, а не аннотацией типа.

Аннотация T|null нарушает существующий код, потому что в ней указывается, допускают ли типы значение NULL по умолчанию.

@jbondc
Интересно ... Я еще не очень внимательно изучал ваше предложение, но думаю, что изучу.

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

@NoelAbrahams
Все предложения с нулевым принудительным исполнением являются критическими изменениями. Даже ?T которое вы описали. Я показал несколько примеров, почему несколько комментариев выше.

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

На самом деле меня это устраивает. С TS 1.5 я как бы смогу получить то, что хочу:

function isVoid(item:any): item is void { return item == null; }
declare externalUnsafeFunction(...):string|void

function test() {
  var res = externalUnsafeFunction(...);
  var words = res.split(' '); // error
  if (!isVoid(res)) {
    var words = res.split(' '); // ok
  }
}

теперь проверка isVoid принудительно выполняется для возвращаемых значений для externalUnsafeFunction или любой другой функции или определения функции, где я добавляю |void к типу возврата.

Я по-прежнему не смогу объявлять функции, которые не принимают значения null / undefined, но можно проявить достаточно усердия, чтобы не забыть инициализировать локальные переменные и члены класса.

Поскольку @NoelAbrahams мы уже обсуждали, что даже с нулевым типом, null все равно должно быть разрешено неявное приведение к любому другому типу, если только будущий флаг компилятора не изменит это.

Также это означает, что в будущем мы можем ввести флаг компилятора, который позволит нам аннотировать, где и когда функция может принимать значение null.

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

@spion , на самом деле это хороший уже «неправильные» , добавление нулевого типа, предложенного вами, с моими предлагаемыми модификациями ничего не изменит.

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

Я предупреждаю, что мы никоим образом не рассматриваем T|void как синтаксис, который мы боялись бы нарушить в будущем. Это почти чушь.

@RyanCavanaugh Это

Вздох.

Наш код слишком велик для запуска в зависимости от T|void если он скоро сломается и не будет заменен. И я не смогу убедить других программистов использовать его, если через неделю он может сломаться.

Хорошо, @spion, если вы когда-нибудь сделаете свой патч, дайте мне знать, и я проверю его на базе кода моей работы. Я могу хотя бы дать статистику о том, сколько ошибок это вызывает.

@RyanCavanaugh чушь, правда? В каком смысле? И что бы вы посоветовали выразить обнуляемые типы, которые должны быть запрещены для любого вида потребления до проверки на null / undefined?

У меня действительно есть много библиотек, которым это могло бы пригодиться.

@Griffork в любом случае будет невозможно безопасно удалить void из объединения до 1.5, не без защиты определяемого пользователем типа, поэтому использование этого будет возможно только после 1.5 (если это вообще станет возможным)

Ерунда, а напишете ли вы когда-нибудь этот код?

var foo: void;
var bar: void = doStuff();

Конечно, нет. Так в чем же смысл добавления void к объединению возможных типов? Обратите внимание на эту часть спецификации языка, которая существует довольно давно в разделе, описывающем The Void Type (3.2.4):

_ПРИМЕЧАНИЕ: мы могли бы рассмотреть вопрос о запрете объявления переменных типа Void, поскольку они не служат никакой полезной цели. Однако, поскольку Void разрешен в качестве аргумента типа для универсального типа или функции, невозможно запретить свойства или параметры Void.

Этот вопрос:

_А что бы вы посоветовали выразить обнуляемые типы, которые должны быть запрещены для любого вида потребления до проверки на null / undefined? _

это то, о чем вся тема. Райан просто указывает, что string|void - это бессмыслица в соответствии с текущей семантикой языка, и поэтому не обязательно принимать зависимость от бессмысленной семантики.

Код, который я написал выше, - это абсолютно действительный TS 1.5, верно?

Приведу пример, который, конечно, не ерунда. У нас есть функция библиотеки базы данных (nodejs), которую мы вызываем get() аналогичную функции Linq Single (), которая, к сожалению, возвращает Promise<null> вместо этого выдает ошибку (отклоненное обещание), когда элемент не найден. Он используется во всей нашей кодовой базе в тысячах мест и вряд ли скоро будет заменен или исчезнет. Я хочу написать определение типа, которое заставит меня и других разработчиков использовать защиту типа перед использованием значения, потому что у нас были десятки трудно отслеживаемых ошибок, возникающих из-за того, что нулевое значение перемещалось далеко в базу кода, прежде чем оно было использовано неправильно.

interface Legacy { 
  get<T>(...):Promise<T|void>
}

function isVoid(val: any): val is void { return val == null; } // also captures undefined

legacy.get(...).then(val => {
  // val.consumeNormally() is a type error here
  if (!isVoid(val)) { 
    val.consumeNormally(); // OK
  }
  else { handle null case }
});

Мне это кажется вполне разумным. Добавление void к объединению приводит к тому, что все операции над val становятся недействительными, как и должно быть. Typeguard сужает тип, удаляя |void и оставляя часть T .

Возможно, в спецификации не учтены последствия недействительности союзов?

Не запрещайте пустые переменные! Они работают как значения типа единицы и могут использоваться
в выражениях (в отличие от void в C #, который требует 2 набора всего: один
для действий и одной функции). Пожалуйста, оставьте пустоту в покое, хорошо?
27 января 2015 г., 20:54 "Горжи Косев" [email protected] написал:

Код, который я написал выше, - это абсолютно действительный TS 1.5, верно?

Я приведу вам пример, который, конечно, не ерунда. У нас есть
функция библиотеки базы данных, аналогичная функции Linq Single (), которая, к сожалению,
возвращает обещаниевместо этого выдает ошибку (отклоненное обещание), когда
товар не найден. Он используется во всей нашей кодовой базе в тысячах
мест и вряд ли скоро будут заменены или уйдут. Я хочу написать
определение типа, которое заставляет меня и других разработчиков использовать защиту типа
перед потреблением значения, потому что у нас были десятки сложных для отслеживания ошибок
происходит от нулевого значения, перемещающегося далеко в кодовую базу, прежде чем быть
потребляется неправильно.

interface Legacy {
получить(...):Обещать
}
функция isVoid (val: any): val is void {return val == null; } // также захватывает undefined

legacy.get (...). then (val => {
// val.consumeNormally () является ошибкой
if (! isVoid (val)) {
val.consumeNormally (); // В ПОРЯДКЕ
}
else {обрабатывать нулевой регистр}
});

Мне это кажется вполне разумным. Вы можете указать, какая часть
бред какой то?

Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -71767358
.

Я говорю, что T|void по сути бессмысленно прямо сейчас, потому что у нас уже есть правило, согласно которому объединение типа и его подтипа эквивалентно просто супертипу, например, Cat|Animal эквивалентно Animal . Обработка void в качестве замены для значений null / undefined не согласуется, потому что null и undefined уже _входятся в домен T _ для всех T , что означает, что они должны быть подтипами T

Другими словами, T|null|undefined , если бы вы могли это написать, уже было бы свернуто в T . Рассматривать void как заклинание для null|undefined неправильно, потому что, если бы это действительно было так, компилятор уже свернул бы T|void в T .

Я бы лично прочитал это http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf

Единственные возможные значения для типа Void - null и undefined. Тип Void является подтипом типа Any и надтипом типов Null и Undefined, но в остальном Void не связан со всеми другими типами.

В виде; в T|void , T конкретно _не_ супертип void потому что единственный супертип (согласно спецификации) - это any .

Изменить: запретить это - другая проблема.

Edit2: перечитайте то, что вы сказали, и это имеет смысл буквально (так же, как документация сформулирована как void), но это не имеет смысла в том, что считается общей (или правильной) интерпретацией документации.

@RyanCavanaugh Я все равно буду использовать эту функцию, пока она не сломается, так как тем временем она может помочь мне найти много ошибок.

И я не могу не указать на иронию утверждения, что сворачивание T|null|undefined в T «имеет смысл», в то время как существование T|void - нет. (Я знаю, дело в спецификации, но все же ...)

@jbondc

В ECMAScript нет статических структурных типов, поэтому этот аргумент недопустим. В ECMAScript все значения имеют тип any поэтому все может быть присвоено чему угодно или передано чему угодно. Этот факт не должен иметь никакого отношения к системе статических типов TypeScript, которая предназначена для улучшения несуществующей в JS.

Немного грустно, что мне приходится объяснять иронию, но вот что:

  1. у вас есть значение, которое не поддерживает ни методов, ни свойств, null
  2. вы создаете систему типов, в которой это значение может быть присвоено переменным любого типа, большинство из которых поддерживает различные методы и свойства (например, числа строк или сложные объекты var s:string = null )
  3. вы создаете тип void который правильно принимает это значение и не допускает никаких методов или свойств, и, как следствие, T|void допускает никаких методов и свойств.

Утверждают, что (3) - вздор, а (2) - нет. Вы видите здесь иронию? Если нет, я попробую другой пример:

  1. у вас есть значения, которые поддерживают только некоторые методы и свойства {}
  2. вы создаете систему типов, в которой это значение может быть присвоено переменным любого типа (например, числовым строкам или сложным объектам var s:string = {} ), которые имеют гораздо больший набор методов и свойств
  3. у вас есть тип {} который правильно принимает значения {} и не допускает никаких методов или свойств, кроме нескольких встроенных, а T|{} естественно, не допускает никаких методов или свойств, кроме встроенные из {}

Теперь, что чепуха, (2) или (3)?

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

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

@spion , именно об этом я и думал.

Здесь, я думаю, это немного прояснит ситуацию:
Согласно спецификации :


Что недействительно?

Тип Void, на который ссылается ключевое слово void, представляет отсутствие значения и используется как тип возвращаемого значения функций без возвращаемого значения.

Итак, void не равно null | undefined.

Единственные возможные значения для типа Void - null и undefined.

Итак, null и undefined могут быть присвоены void (поскольку они могут быть назначены практически чему угодно).


Null - это тип.

3.2.5 Нулевой тип
Тип Null соответствует одноименному примитивному типу JavaScript и является типом нулевого значения.
буквальный.
Литерал null ссылается на одно-единственное значение типа Null. Невозможно напрямую сослаться на сам тип Null.


Неопределенный - это тип.

3.2.6 Неопределенный тип
Тип Undefined соответствует одноименному примитивному типу JavaScript и является типом неопределенного литерала.


Пустота - это только супертип нулевого и неопределенного _типов_

. Тип Void является подтипом типа Any и надтипом типов Null и Undefined, но в остальном Void не связан со всеми другими типами.


Итак, @jbondc в соответствии со спецификацией языка any ). Фактически, Void специально написан, чтобы иметь другое поведение по сравнению с типами Null и Undefined:

Пустота:

Тип Void является подтипом типа Any и надтипом типов Null и Undefined, но в остальном Void не связан со всеми другими типами.

Нулевой:

Тип Null - это подтип всех типов, кроме типа Undefined. Это означает, что null считается допустимым значением для всех типов примитивов, типов объектов, типов объединения и параметров типа, включая даже типы примитивов Number и Boolean.

Неопределенный:

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

Теперь яснее?

То, что мы просим, ​​- это тип, который заставляет охранять нули, и тип, который заставляет охранять неопределенные значения. Я не прошу, чтобы null или undefined нельзя было назначать другим типам (это слишком ломает), просто возможность выбрать дополнительную разметку. Черт возьми, их даже не нужно называть Null или Undefined (типы).

@jbondc, вот ирония: что бы ни говорил компилятор TS, значения null и undefined в JS никогда не будут поддерживать какие-либо методы или свойства (кроме исключения исключений). Следовательно, делать их присваиваемыми любому типу - это ерунда, примерно такая же чепуха, как присвоение значения {} строкам. Тип void в TS не содержит значений, _but_ null или undefined назначаются всем и, следовательно, могут быть присвоены переменным типа void, так что здесь есть еще одна чепуха. И в этом ирония - то, что (через охранников и союзов) в настоящее время они объединяются во что-то, что действительно имеет смысл :)

Такая сложная вещь, как компилятор TS, была написана без защиты, вот о чем стоит подумать.

Существует довольно большое приложение, написанное на PHP или JAVA, которое не делает язык лучше или хуже.
Египетские пирамиды были построены без какой-либо современной машины. Означает ли это, что все, что мы изобрели за последние 4500 лет, - дерьмо?

@jbondc См. мой комментарий выше со списком проблем в компиляторе TypeScript, вызванных значениями null и undefined (за вычетом первого в списке)

@jbondc он не должен решать мировые проблемы, это должен быть еще один инструмент, доступный в распоряжении разработчика.
Это будет иметь такое же влияние и полезность, как перегрузки функций, объединения и псевдонимы типов.
Это еще один вариант синтаксиса, который позволяет разработчикам более точно вводить значения.

Одна из серьезных проблем, упомянутых для создания типов, не допускающих значения NULL, - это то, что делать с существующим кодом TypeScript. Здесь есть две проблемы: 1) заставить существующий код работать с новым компилятором, который поддерживает типы, не допускающие значения NULL, и 2) взаимодействие с кодом, который не был обновлен - избегая болота стиля Python 2/3.

Тем не менее, должно быть возможно предоставить инструмент, который выполняет автоматическую перезапись существующего кода TS, чтобы заставить его строиться, и в этом ограниченном случае что-то, что работает намного лучше, чем, скажем, 2to3 для Python.

Возможно, постепенная миграция могла бы выглядеть примерно так:

  1. Ввести поддержку синтаксиса '? T', чтобы указать, возможно, нулевой тип. Первоначально это не влияет на проверку типов, это просто для документации.
  2. Предоставьте инструмент, который автоматически перезаписывает существующий код для использования '? T'. Самая глупая реализация преобразует все случаи использования буквы «T» в «? T». Более разумная реализация проверила бы, не предполагал ли код, который использовал тип, неявно, что он ненулевой, и в этом случае использовала бы букву T. Для кода, который пытается присвоить значение типа '? T' параметру или свойству, ожидающему 'T', это может заключить значение в заполнитель, который утверждает (во время компиляции), что значение не равно нулю - например, let foo: T = expect(possiblyNullFoo) или let foo:T = possiblyNullFoo!
  3. Примените инструмент ко всем предоставленным определениям типов и определениям в репозитории ОпределенноТипед.
  4. Введите предупреждение компилятора при попытке назначить '? T' слоту, ожидающему 'T'.
  5. Позвольте предупреждениям выпекаться и убедитесь, что переносом можно управлять, и посмотрите, как будут приняты изменения.
  6. Сделайте присвоение '? T' слоту, ожидающему ошибки 'T', в основной новой версии компилятора.

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

В предыдущем комментарии я предполагаю, что не будет режима для выбора обнуляемости простого 'T', чтобы избежать разделения языка, и что шаги 1-3 будут полезны сами по себе, даже если шаг 6 никогда не окажется практичным.

Мои случайные мысли:

  1. Часть expect() может быть сложной, ее нужно хорошо продумать. Есть не только назначения, но и интерфейсы, универсальные шаблоны или типы параметров ...
  2. Я _think_ TS не имеет никаких предупреждений, только ошибки. Если я прав, я не уверен, что они будут вводить предупреждения только для этой функции.
  3. Даже если многие люди обновят свой код, предприятия могут делать это очень медленно или даже никогда не захотят этого делать в случае больших существующих кодовых баз. Это делает этот основной выпуск огромным изменением, чего не хочет команда TS.

Тем не менее, я все еще думаю, что флаг «opt-in» сообщать об нулевых ошибках является приемлемым решением. Проблема в том, что один и тот же код / ​​определения должны иметь такое же поведение (за вычетом ошибок), когда флаг выключен, что у меня еще не было времени подумать.

@ jods4 - Что касается части expect() , я считаю, что везде, где у вас есть ?T компилятор видит T | undefined чтобы вы могли повторно использовать правила для обработки типов объединения, насколько это возможно. возможный.

Я согласен, я не уверен, насколько реалистичен «день флага» для TypeScript. Помимо флага отказа, это также может быть флаг отказа, который в конечном итоге становится флагом отказа. Важно иметь четкое представление о том, что такое идиоматический стиль.

Что-то еще, имеющее отношение к этому обсуждению - https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf обсуждает исследования, проведенные командой Chrome по использованию информации о типе (указанной в стиле TypeScript аннотации) в компиляторе для оптимизации JS. Здесь есть открытый вопрос о допустимости значений NULL - они хотели бы типы, не допускающие значения NULL, но также не уверены в осуществимости.

Просто вернусь сюда. Я удивлен, что мы еще не решили эту проблему.

Я говорю, что здесь есть дополнительная ценность. Тот, который применим только во время компиляции.
Вместо того, чтобы писать больше кода для проверки значения null, возможность объявить значение как не допускающее значение null может сэкономить время и код. Если я описываю тип как «не допускающий значения NULL», то он должен быть инициализирован и, возможно, утвержден как ненулевой, прежде чем будет передан другой функции, которая ожидает ненулевого значения.

Как я уже сказал выше, возможно, это можно просто решить путем реализации контрактов кода.

Почему вы не можете просто отказаться от использования нулевого литерала и охранять все места, где
нули могут просочиться в ваш код? Таким образом вы будете в безопасности от нуля
ссылочное исключение без необходимости везде проверять значение null.
20 февраля 2015 г., 13:41, "electricessence" [email protected] написал:

Просто вернусь сюда. Я удивлен, что мы еще не решили
Вот этот.

Я говорю, что здесь есть дополнительная ценность. Тот, который применим только при компиляции
время.
Вместо того, чтобы писать дополнительный код для проверки значения null, можно
объявление значения как не допускающего значения NULL может сэкономить время и код. Если я опишу
введите как «не допускающий значения NULL», тогда он должен быть инициализирован и, возможно, утвержден
как ненулевое значение перед передачей в другую функцию, которая ожидает
ненулевой.

Как я уже сказал выше, возможно, это можно просто решить с помощью кодовых контрактов.
выполнение.

Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204
.

Нулевые проверки выполняются значительно быстрее, чем неопределенные.
21.02.2015, 11:54, «Алексей Быков» [email protected] написал:

Почему вы не можете просто отказаться от использования нулевого литерала и охранять все места, где
нули могут просочиться в ваш код? Таким образом вы будете в безопасности от нуля
ссылочное исключение без необходимости везде проверять значение null.
20 февраля 2015 г., 13:41, "electricessence" [email protected]
написал:

Просто вернусь сюда. Я удивлен, что мы еще не решили
Вот этот.

Я говорю, что здесь есть дополнительная ценность. Тот, который применим только при компиляции
время.
Вместо того, чтобы писать дополнительный код для проверки значения null, можно
объявление значения как не допускающего значения NULL может сэкономить время и код. Если я опишу
введите как «не допускающий значение NULL», тогда он должен быть инициализирован и, возможно,
утверждал
как ненулевое значение перед передачей в другую функцию, которая ожидает
ненулевой.

Как я уже сказал выше, возможно, это можно просто решить с помощью кодовых контрактов.
выполнение.

Ответьте на это письмо напрямую или просмотрите его на GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75295204>
.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346198
.

Я считаю, что эти проверки не нужны (если ключевое слово null запрещено
и все остальные места, куда он может попасть, охраняются).

Почему? 1. Код без проверок работает значительно быстрее, чем код.
с проверками на нули. 2. Он содержит меньше if и, следовательно, более читабельный.
и ремонтопригодный.
20 февраля 2015 г., 19:57, "Griffork" [email protected] написал:

Нулевые проверки выполняются значительно быстрее, чем неопределенные.
21.02.2015, 11:54, «Алексей Быков» [email protected] написал:

Почему вы не можете просто отказаться от использования нулевого литерала и охранять все места, где
нули могут просочиться в ваш код? Таким образом вы будете в безопасности от нуля
ссылочное исключение без необходимости везде проверять значение null.
20 февраля 2015 г., 13:41, "electricessence" [email protected]
написал:

Просто вернусь сюда. Я удивлен, что мы еще не решили
Вот этот.

Я говорю, что здесь есть дополнительная ценность. Тот, который применим только при компиляции
время.
Вместо того, чтобы писать дополнительный код для проверки значения null,
к
объявление значения как не допускающего значения NULL может сэкономить время и код. Если я
описать
введите как «не допускающий значение NULL», тогда он должен быть инициализирован и, возможно,
утверждал
как ненулевое значение перед передачей в другую функцию, которая ожидает
ненулевой.

Как я уже сказал выше, возможно, это можно просто решить с помощью кодовых контрактов.
выполнение.

Ответьте на это письмо напрямую или просмотрите его на GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

Ответьте на это письмо напрямую или просмотрите его на GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346198>
.

Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346390
.

Вы упускаете суть, мы используем нули, чтобы избежать неопределенных проверок.
А поскольку у нашего приложения нет одного согласованного состояния, у нас есть, и
часто приходится иметь значение null / undefined вместо объекта для
представляют это.
21.02.2015 12:20 "Алексей Быков" [email protected] написал:

Я считаю, что эти проверки не нужны (если ключевое слово null запрещено
и все остальные места, куда он может попасть, охраняются).

Почему? 1. Код без проверок работает значительно быстрее, чем код.
с проверками на нули. 2. Он содержит меньше if и, следовательно, более читабельный.
и ремонтопригодный.
20 февраля 2015 г., 19:57, "Griffork" [email protected] написал:

Нулевые проверки выполняются значительно быстрее, чем неопределенные.
21.02.2015, 11:54, "Алексей Быков" [email protected]
написал:

Почему вы не можете просто отказаться от использования нулевого литерала и охранять все места
где
нули могут просочиться в ваш код? Таким образом вы будете в безопасности от нуля
ссылочное исключение без необходимости везде проверять значение null.
20 февраля 2015 г., 13:41, "electricessence" [email protected]
написал:

Просто вернусь сюда. Я удивлен, что мы на самом деле не
решено
Вот этот.

Я говорю, что здесь есть дополнительная ценность. Тот, который применим только в
компилировать
время.
Вместо того, чтобы писать дополнительный код для проверки значения null,
способный
к
объявление значения как не допускающего значения NULL может сэкономить время и код. Если я
описать
введите как «не допускающий значение NULL», тогда он должен быть инициализирован и, возможно,
утверждал
как ненулевое значение перед передачей в другую функцию, которая ожидает
ненулевой.

Как я сказал выше, возможно, это можно просто решить с помощью кода
контракты
выполнение.

Ответьте на это письмо напрямую или просмотрите его на GitHub
<

https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

Ответьте на это письмо напрямую или просмотрите его на GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346198

.

Ответьте на это письмо напрямую или просмотрите его на GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346390>
.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75347832
.

Вам тоже не хватает моих очков. Чтобы сохранить согласованность вашего состояния и иметь возможность представлять отсутствующие значения, вам не нужно использовать значения NULL или undefined. Есть и другие способы, особенно теперь с поддержкой типов объединения:

class Nothing { public 'i am nothing': Nothing; }
class Something { 
    constructor(
    public name: string,
    public value: number
   ) { }
}
var nothing = new Nothing();
var whoami = Math.random() > 0.5 ? new Something('meh', 66) : nothing;

if (whoami instanceof Nothing) {
    console.log('i am a null killer');
} else if (whoami instanceof Something) {
    console.log(whoami.name + ': ' + whoami.value);
}

Итак, мы просто закодировали отсутствующее значение без использования null или undefined. Также обратите внимание, что мы делаем это на 100% явным образом . Благодаря типу охранников невозможно пропустить проверку до считывания значения.

Как это круто?

Считаете ли вы, что экземпляр проверки быстрее, чем проверка нуля, для
приложение, которое должно делать сотни таких в секунду?
Скажу так: нам пришлось создать псевдонимы для функций, потому что
быстрее не использовать '.' аксессуары.
21.02.2015 12:58 "Алексей Быков" [email protected] написал:

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

class Nothing {public 'я ничто': Nothing; }
class Something {
конструктор(
публичное имя: строка,
публичное значение: число
) {}
}
вар ничего = новое ничего ();
var whoami = Math.random ()> 0,5? new Something ('meh', 66): ничего;

if (whoami instanceof Nothing) {
// whoami - это ничто
console.log («Я убийца нулевых»);
} else if (whoami instanceof Something) {
// whoami - это экземпляр чего-то
console.log (whoami.name + ':' + whoami.value);
}

Итак, только что было закодировано отсутствующее значение без использования null или undefined.
Также обратите внимание, что мы делаем это _100% явным образом_. Спасибо, чтобы набрать
охранников, нельзя пропустить проверку до считывания значения.

Как это круто?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75349967
.

@ aleksey-bykov Обратите внимание, что я уже затронул этот вопрос: необходимость моделирования определений типов для существующих библиотек, которые уже используют нулевые значения, не решена. В этом случае вы не можете просто «выйти, используя» нулевые значения.

Кроме того, это даже не касается проблем с неинициализированными переменными, которые будут undefined . В Haskell проблемы даже не существует, потому что вы не можете оставить значение «неинициализированным».

@Griffork , я не думаю, я запускаю тесты, тесты говорят, что это зависит от браузера.
http://jsperf.com/nullable-vs-null-vs-undefined-vs-instanceof
Я думаю, что вам нужно найти баланс между безопасностью и производительностью. Затем вам нужно тщательно измерить свою производительность, прежде чем пытаться ее оптимизировать. Скорее всего, вы исправляете что-то, что не сломано, и ваши нулевые проверки, о которых вы так беспокоитесь, могут составлять менее 2% от общей производительности, в таком случае, если вы сделаете это вдвое быстрее, вы получите только 1%.

@spion ,

Наша кодовая база - это более 800 файлов TypeScript и более 120000 строк кода.
и нам никогда не нужны были null или undefined, когда дело касалось модельного бизнеса.
сущности домена. И хотя нам пришлось использовать нули для манипуляций с DOM,
все такие места тщательно изолированы, чтобы нули не могли просочиться
дюйм. Я не верю вашему аргументу о том, что могут понадобиться нули, есть
готовые к производству языки без нулей вообще (Haskell) или с нулем
забанен (F #).
22 февраля 2015 г. в 9:31 «Джон» [email protected] написал:

@ aleksey-bykov https://github.com/aleksey-bykov Из практического
перспективы, вы не можете игнорировать null. Вот некоторая предыстория того, почему вы
в любом случае нужен тип, допускающий значение NULL:
https://www.google.com/patents/US7627594?ei=P4XoVPCaEIzjsATm4YGoBQ&ved=0CFsQ6AEwCTge

Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75437927
.

Да, и самое важное, что вы можете понять здесь, - это то, что мы НЕ моделируем сущности бизнес-домена, мы ОЧЕНЬ чувствительны ко времени, и наша кодовая база не намного меньше вашей.

Для вашей цели вам не нужны нули, и это нормально, вы можете продолжать использовать Typescript без них.
Для нашей цели нам нужны нули, и мы не можем позволить себе этого не делать.

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

Если вы также читаете выше, предложение, не допускающее значения NULL, также распространяется на undefined.

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

Опять же, вероятно, не стоит беспокоиться, если (как мы) вы не выполняете 10 000 проверок менее чем за 10 мс.

(И да, мы действительно изменились из-за отслеживания производительности нашего приложения и поиска узких мест)

Я хотел бы спровоцировать дискуссию по этому поводу. Буквально вчера в видео о сборке я видел анализаторы, реализованные на C #. Я открыл проблему № 3003, чтобы реализовать ее аналогично TypeScript.

По сути, анализатор - это библиотека, которая может выполнять дополнительные проверки синтаксиса, тегировать строки как ошибки / предупреждения и интерпретировать языковую службу. Это эффективно позволит принудительно выполнять нулевые проверки с помощью внешней библиотеки.

На самом деле я вижу, что анализаторы в TypeScript были бы хороши и для других целей. Есть действительно странные библиотеки, больше, чем для C #.

Для тех, кто следит за этой проблемой: я поддерживаю библиотеку под названием monapt, которая позволяет вам объявлять обнуляемость переменной. Он включает определения Typescript, поэтому все проверяется во время компиляции.

Обратная связь приветствуется!

Я просто прочитал всю ветку.

Я предполагаю, что в предложении должны использоваться термины non-voidable и voidable , потому что void в спецификации TS - это тип для значений null и undefined . (Я предполагаю, что предложение также касается неопределенного значения: smile :)

Я также согласен с тем, что доступ undefined или null - это корень всего зла в программировании, добавление этой функции сэкономит людям бесчисленное количество часов.

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

Хотя я могу согласиться с тем, что ADT можно использовать вместо типов, допускающих значение NULL; как мы можем быть уверены, что переданный нами ADT сам не является нулевым? Любой тип в TS может быть нулевым, включая ADT, верно? Я считаю это серьезным недостатком в представлении ADT как о решении проблемы типов, не допускающих значения NULL (не подлежащих отмене). ADT работают так же хорошо, как и на языках, которые разрешают (или запрещают) типы, не допускающие значения NULL.

Другой ключевой вопрос - это определения библиотек. У меня может быть библиотека, которая ожидает строку в качестве аргумента, но компилятор TS может ввести check, передав null функции! Это просто неправильно ...

Я считаю это серьезным недостатком в представлении ADT как о решении проблемы ...

  1. получить доступ к ADT через шаблон проектирования (из-за отсутствия собственных
    поддержка от TypeScript)
  2. запретить ключевое слово null
  3. охранять все места, где нули могут просочиться в ваш код извне
  4. Наслаждайся проблемой null ушла навсегда

В нашем проекте нам удалось выполнить шаги 1, 2 и 3. Угадайте, что мы делаем.
в настоящее время.

@ aleksey-bykov

Как у вас (2) с undefined ? Это может возникнуть в различных ситуациях, которые не связаны с ключевым словом undefined : неинициализированные члены класса, необязательные поля, отсутствующие операторы возврата в условных ветвях ...

Сравните машинописный текст с типом потока . По крайней мере, поток имеет дело с большинством случаев.

Кроме того, если вы хотите сказать: «Забудьте об общих идиомах в языке. Мне все равно, я буду изолировать себя от остального мира», тогда вам, вероятно, лучше использовать purescript .

Это может возникнуть в различных ситуациях, которые не связаны с ключевым словом undefined ...

  • неинициализированные члены класса - классы запрещены
  • необязательные поля - необязательные поля / параметры запрещены
  • отсутствующие операторы возврата - настраиваемое правило tslint, которое гарантирует, что все пути выполнения возвращаются

лучше использовать purescript

  • я бы хотел, но код, который он генерирует, меньше всего влияет на производительность

@ aleksey-bykov забавно, что вы упоминаете о производительности, потому что замена типов, допускающих значение NULL, на ADT приводит к значительным накладным расходам процессора и памяти по сравнению с простыми проверками NULL (например, flowtype). То же самое касается неиспользования классов (может быть непомерно дорогим, если вы создаете много объектов с большим количеством методов на них)

Вы упомянули об отказе от необязательных полей, но они используются очень многими библиотеками JS. Что вы сделали с этими библиотеками?

Вы также упоминаете, что не используете классы. Я полагаю, вы также избегаете библиотек, которые полагаются (или будут полагаться) на классы (например, React)?

В любом случае, я не вижу причин отказываться от такого количества функций, когда вполне возможно реализовать совершенно разумное решение (которое действительно соответствует базовому нетипизированному языку).

забавно, что вы думаете, что единственная цель ADT - представить отсутствующее значение, которое в противном случае было бы закодировано нулевым

я бы предпочел сказать, что исчезновение "нулевой" проблемы (и классов тоже) является побочным продуктом всех хороших вещей, которые ADT приносит нам

удовлетворяя ваше любопытство, для критических частей производительности, таких как узкие циклы и большие структуры данных, мы используем так называемую абстракцию Nullable<a> , обманывая TypeScript, заставляя думать, что он имеет дело с обернутым значением, но на самом деле (во время выполнения) такое значение просто примитив, которому разрешено принимать значения NULL, существует специальный набор операций с Nullable, который предотвращает утечку этого NULL

дайте мне знать, если вы хотите узнать подробности

interface Nullable<a> {
    'a nullable': Nullable<a>;
    'uses a': a;
}
/*  the following `toNullable` function is just for illustration, we don't use it in our code,
    because there are no values capable of holding naked null roaming around,
    instead we just alter the definition of all unsafe external interfaces:
    // before
    interface Array<T> {
       find(callback: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T;
    }
    // after
    interface Array<a> {
       find(callback: (value: a, index: number, array: a[]) => boolean, thisArg: any): Nullable<a>;
    }
*/
function toNullable<a>(value: a) : Nullable<a> {
    return <any>value;
}
function toValueOrDefault<a>(value: Nullable<a>, defaultValue: a)  : a {
    return value != null ? <any>value : defaultValue;
}

Зачем использовать рекурсивную структуру типов? 'a nullable': a должно хватить, не так ли?

Я полагаю, у вас также есть callFunctionWithNullableResult<T,U>(f: (T) => U, arg:T):Nullable<U> (включая все другие перегрузки для функций с разной арностью) для работы со всеми внешними функциями, возвращающими нули?

И если ключевое слово null запрещено линтером, как получить " Nothing ", хранимое в обнуляемом? у вас особый случай toNullable(null) ?

Я знаю, что такое алгебраические типы данных. Можете ли вы дать мне пример (кроме проверок полноты и неуклюжести), в котором ADT (или, скорее, типы сумм) + сопоставление с образцом могут делать то, чего не могут сделать объединения машинописных текстов + средства защиты определяемых пользователем типов?

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

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

Я лично ненавижу перегруженность, это означает, что я использую 2 отдельных псевдополя, каждое из которых решает отдельную проблему

Я полагаю, у вас также есть callFunctionWithNullableResult(f: T -> U, arg: T) => Обнуляемый

нет, как я уже сказал, мы изменяем файлы определения (* .d.ts), заменяя все, что у нас есть свидетельство, может быть нулевым во внешнем коде, на "оболочку" Nullable , см. пример в комментарии в моем предыдущем сообщении

как получить "Ничего", хранящееся в обнуляемом

мы не храним значения NULL в значениях Nullable, значения NULL поступают с сервера (которым мы ограниченно контролируем) или из внешнего API, как только он находится в нашем коде, он обернут в Nullable, но наш код не может производить обнуляемый по желанию, мы можем только преобразовать данное обнуляемое значение в другое обнуляемое, но не создать его из воздуха

Союзы машинописных текстов + охранники типов, определенные пользователем, не могут

ха! Я совсем не фанат профсоюзов и могу часами говорить о том, что делает их плохой идеей

возвращаясь к вашему вопросу, дайте мне многоразовый эквивалент Either a b с использованием объединений и защиты типов
(подсказка: https://github.com/Microsoft/TypeScript/issues/2264)

Типы в вашем примере не являются более номинальными только потому, что они рекурсивны - они остаются «псевдо-номинальными».

Однако, чтобы ответить на ваш вопрос: вы не реализуете общий Either именно потому, что типы не являются номинальными. Вы просто используете

type MyType = A | B

а затем используйте охранники типа для A и B ... В качестве бонуса это работает и со всеми существующими библиотеками JS: например, идиоматический способ проверки Thenable - это если есть - это метод then прикрепленный к объекту. Подойдет ли для этого охранник шрифта? Несомненно.

Ха! Это чертовски хороший улов. Что ж, должно быть, что-то изменилось в последних версиях компилятора. Вот пуленепробиваемый номинал:

enum GottaBeNonimalBrand {}
interface GottaBeNonimal {
     branded: GottaBeNonimalBrand;
}
function toGottaBeNominal() : GottaBeNominal {
   return <any> {};
}

Подожди секунду! Вы обманули ! Старый добрый способ по-прежнему работает как шарм.

Однако, чтобы ответить на ваш вопрос: вы не реализуете общий Either именно потому, что типы не являются номинальными. Вы просто используете ...

приятно то, что ты говоришь, ну как там Either хотя? что подлинный Either является ОБЩИМ, и вокруг него около 30 стандартных вспомогательных функций, теперь вы говорите, что мне нужно решить (номинально) и иметь до 30 x number_of_combinations_of_any_2_types_in_my_app этих функций для всех возможных пар, которые я мог бы сталкиваться?

Надеюсь, эта ветка все еще отслеживается людьми из команды TS. Хочу попробовать перезагрузить!
Мне кажется, что ненулевые типы застряли, когда стало очевидно, что невозможно ввести это неразрывным способом. Команда C # думает о ссылочных типах, не допускающих значения NULL, и, конечно же, сталкивается с теми же проблемами, особенно с совместимостью. Мне нравятся идеи, которые они изучают в настоящий момент, и мне интересно, применимы ли они к TS.

Мозговой штурм C # vnext (кратко)

По сути, в C # у вас есть типы значений, отличные от NULL, такие как int . Затем в C # 2.0 мы получили типы значений, допускающие значение NULL, такие как int? . Ссылки всегда допускали значение NULL, но они думают об изменении этого и применении того же синтаксиса: string никогда не имеет значения NULL, string? может быть.

Конечно, большая проблема в том, что это меняет значение string и нарушает код. Если было нормально присвоить / вернуть null объекту string , теперь это ошибка. Команда C # думает об «отключении» этих ошибок, если вы не согласитесь. Добавление точки в string? всегда является ошибкой, потому что синтаксис новый и ранее не разрешался (следовательно, это не критическое изменение).
Другая проблема связана с другими библиотеками и т. Д. Чтобы решить эту проблему, они хотят установить флаг согласия для каждого файла или сборки. Таким образом, внешний код, который выбирает более строгую систему типов, получает преимущества, а старый устаревший код и библиотеки продолжают работать.

Как это могло быть перенесено на TS?

  1. Мы вводим флаг включения ненулевых типов. Он может быть глобальным (передается компилятору) или для каждого файла. Это всегда должно быть для каждого файла для .d.ts .
  2. Базовые типы не допускают значения NULL, например number . Появился новый тип null .
  3. number? - это просто ярлык для number | null .
  4. Эта функция вводит новые ошибки для типов, допускающих значение NULL, например (x: string?) => x.length без защиты типа.
  5. Эта функция вводит новые ошибки для типов, не допускающих значения NULL, например assign let x: string = null;
  6. При манипулировании ненулевым типом, который объявлен вне области действия согласия, ошибки, указанные в пункте 5., игнорируются. _Это немного отличается от того, что string по-прежнему считается string? ._

Что в этом хорошего?

Когда я пишу новый код, я могу подписаться и получить полные статические проверки NULL в моем коде и в обновленных определениях библиотек. Я могу продолжать использовать определения устаревших библиотек без ошибок.

Когда я использую старый код, я могу подписаться на новые файлы. Я все равно могу объявить string? и получить ошибки при доступе к членам без надлежащей проверки на null. Я также получаю преимущества и строгую проверку для библиотек, определения которых были обновлены. После включения .d.ts передача null функции, определенной как length(x: string): number становится ошибкой.
_ (Примечание: передача string? является ошибкой, хотя передача string из кода отказа - нет.) _

Пока я не соглашусь, никаких критических изменений не будет.

Что вы думаете?

Конечно, есть что подумать о том, как эта функция будет работать на практике. Но может ли команда TS вообще рассмотреть такую ​​схему подписки?

удивительно, как люди хотят более быструю лошадь вместо автомобиля ...

Не нужно быть снисходительным. Я читал ваши комментарии об ADT и не думаю, что они мне нужны. Мы можем обсудить это, если хотите, но в новом выпуске «Поддержка ADT в TS». Вы можете написать там, чем они хороши, почему они нам нужны и как они устраняют потребность в типах, не допускающих значения NULL. Эта проблема касается типов, не допускающих значения NULL, и вы действительно захватываете тему.

то, о чем вы говорите, называется распространением констант и, если оно реализовано, не должно ограничиваться только двумя случайными константами, которые оказались null и undefined . То же самое можно сделать со строками, числами и всем остальным, и в этом есть смысл, иначе это было бы просто для воспитания нескольких старых привычек, от которых некоторые люди не хотят полностью относиться.

@ aleksey-bykov и, конечно же, чтобы улучшить ограничения типов, вы реализуете более высокодородные типы, а для получения производных экземпляров вы реализуете классы типов и так далее и тому подобное. Как это согласуется с целью проектирования TypeScript «согласование с ES6 +»?

И все равно бесполезно решать исходную задачу из этого выпуска. Его легко реализовать в TS с использованием общих классов даже сегодня (без типов объединения). Это будет примерно так же полезно, как добавление Optional в Java, а это: почти. Потому что кто-то просто назначит где-то null или ему понадобятся дополнительные свойства, и сам язык не будет жаловаться, но все будет взорваться во время выполнения.

Мне непонятно, почему вы настаиваете на злоупотреблении языком, чтобы заставить его изгибаться способами, для которых он никогда не был предназначен. Если вам нужен функциональный язык с алгебраическими типами данных и без нулевых значений, просто используйте PureScript. Это превосходный, хорошо спроектированный язык с функциями, которые согласованно сочетаются друг с другом, проверенный на протяжении многих лет в Haskell без накопленных недостатков Haskell (принципиальная иерархия классов типов, реальные записи с полиморфизмом строк ...). Мне кажется, что намного проще начать с языка, который соответствует вашим принципам и требованиям, а затем настраивать производительность, а не начинать с чего-то, чего нет (и никогда не было предназначено), а затем крутить, поворачивать и ограничивать, чтобы заставить его работать по-своему.

В чем проблема с производительностью PureScript? Это закрытие, вызванное повсеместным каррированием?

@ jods4 означает, что все существующие в настоящее время файлы .d.ts необходимо обновить с помощью флага неработающего, или что настройка всего проекта не влияет на .d.ts (и .d.ts без флага всегда считается «старым» способом.
Насколько это понятно (вызовет ли это проблемы для людей, работающих над несколькими проектами, или путаницу при чтении файлов .d.ts)?
Как будет выглядеть интерфейс между новым и старым (библиотечным) кодом. Будет ли он завален кучей (в некоторых случаях) ненужных проверок и приведений (вероятно, неплохо, но это может отвлечь новичков от языка)?
В основном мне (сейчас) нравится идея типов, не допускающих значения NULL, и предложение @ jods4 и предложение ! - это единственный способ увидеть, как работают типы, не допускающие значения NULL (игнорируя объявления).

Возможно, он потерян в этой ветке, но какую проблему это решает? Это то, что намекало на меня. Аргументы, которые я недавно видел в этой беседе, - «это есть в других языках». Мне также трудно понять, что это решает, что полезно, что не требует каких-либо махинаций на TypeScript. Хотя я могу понять, что значения null и undefined могут быть нежелательными, я склонен рассматривать это как логику более высокого порядка, которую я хочу вставить в свой код, а не ожидать, что базовый язык будет обрабатывать ее. .

Другой незначительный нюанс заключается в том, что все рассматривают undefined как константу или ключевое слово. Технически это тоже не так. В глобальном контексте существует переменная / свойство undefined примитивного типа undefined . null - это литерал (который также имеет примитивный тип null , хотя это давняя ошибка, реализованная для возврата typeof "object" ). В ECMAScript это две совершенно разные вещи.

@kitsonk
Проблема в том, что все типы могут быть нулевыми, и все типы могут быть неопределенными, но не все операции, которые работают с определенным типом, не работают с нулевыми или неопределенными значениями (например, деление), вызывая ошибки.
Что они хотят, так это возможность указать тип как «является числом и никогда не может быть нулевым или неопределенным», чтобы они могли гарантировать, что при использовании функции, которая может вводить null или undefined, программисты знают, что нужно защищаться от недопустимых значений.
Есть два основных аргумента в пользу возможности указать значение как «этот тип, но не может быть нулевым или неопределенным»:
A) Всем программистам, использующим код, очевидно, что это значение никогда не должно быть нулевым или неопределенным.
Б) компилятор помогает отловить отсутствующие охранники типов.

существует неопределенная переменная / свойство, которое относится к неопределенному примитивному типу. null - это литерал (который также относится к примитивному типу null

@kitsonk - отличный аргумент в пользу этой проблемы; null - это не number , это не больше Person чем Car . Это отдельный тип, и эта проблема заключается в том, чтобы машинописный текст мог делать это различие.

@kitsonk Я думаю, что одна из самых распространенных ошибок - это ошибки с нулевой ссылкой / неопределенным доступом. Возможность указать свойство или переменную, чтобы она не была нулевой / неопределенной, сделает программный код более надежным. А также позвольте таким инструментам, как LS, отлавливать подобные ошибки https://github.com/Microsoft/TypeScript/issues/3692.

Спасибо за разъяснения. Может быть, я снова что-то упускаю, просмотрев это ... Поскольку в TypeScript уже есть аргумент «необязательности», почему следующее не работает как предложение?

interface Mixed {
    optional?: string;
    notOptional: string;
    nonNullable!: string;
}

function mixed(notOptional: string, notNullable!: string, optional?: string): void {}

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

Поскольку в TypeScript уже есть аргумент «необязательность», почему следующее не будет работать как предложение?

Просто чтобы уточнить, какие опции прямо сейчас в TS не являются обязательными только во время инициализации. Null и undefined по-прежнему присваиваются всем типам.

Это несколько не интуитивно понятных случаев необязательных вариантов.

interface A {
   a?: string;
}
let a: A = {} // ok as expected

interface B {
   b: string;
}
let b1: B = { b: undefined } //ok, but unintuitive
let b2: B = { b: null } // ok, but unintuitive
let b3: B = {} // error as expected

Или, чтобы просто не усложнять, optional в TS означает неинициализируемый. Но в этом предложении говорится, что undefined и null не могут быть присвоены недействительному (ненулевому, ненулевому) типу.

? помещается рядом с именем свойства / аргумента, потому что он ссылается на _существование_ этого имени или нет. @tinganho хорошо это объяснил. Расширяя его пример:

function foo(arg: string, another?: string) {
  return arguments.length;
}

var a: A = {};
a.hasOwnProperty('a') // false
foo('yo') // 1, because the second argument is _non-existent_.
foo(null) // this is also ok, but unintuitive

Ненулевое значение ! не связано с _существованием_ имени. Он связан с типом, поэтому его следует размещать по типу. Тип не допускает значения NULL.

interface C {
  c: !string;
}

let c1: C = { }; // error, as expected
let c2: C = { c: null }; // error, finally!
let c3: C = { c: 'str' }; // ok! :)

function bar(arg: !string) {
  return arg.length;
}

bar(null) // type error
bar('foo') // 3

@Griffork Существующий .d.ts будет работать нормально и может плавно обновляться для поддержки более строгих определений.
По умолчанию .d.ts не включаются. Если автор библиотеки обновляет свою библиотеку точными определениями, отличными от NULL, он указывает на это, устанавливая флаг в верхней части файла.
В обоих случаях все просто работает без каких-либо размышлений / настроек со стороны потребителя.

@kitsonk Проблема, которую это решает, заключается в уменьшении количества ошибок, потому что люди предполагают, что что-то не является нулем, хотя это может быть, или передают нули в функции, которые его не поддерживают. Если вы работаете над большими проектами с большим количеством людей, вы можете использовать функции, написанные не вами. Или вы можете использовать сторонние библиотеки, которые вам плохо знакомы. Когда вы сделаете: let x = array.sum(x => x.value) , можете ли вы предположить, что x никогда не будет нулевым? Может быть, это 0, если массив пуст? Откуда вы знаете? Разрешено ли в вашей коллекции иметь x.value === null ? Поддерживается ли это функцией sum() или это будет ошибкой?
С помощью этой функции эти случаи проверяются статически, и передача возможно нулевого значения функции, которая его не поддерживает, или использование возможно нулевого значения без проверки, сообщается как ошибка.
По сути, в полностью типизированной программе больше не может быть "NullReferenceException" (за исключением угловых случаев, связанных с undefined , к сожалению).

Обсуждение необязательных параметров не имеет отношения. Разрешение null или запрета связано с системой типов, а не с объявлениями переменных / параметров, и хорошо интегрируется с защитой типов, типами объединения и пересечения и т. Д.

тип _void_ не имеет значений, но ему могут быть присвоены значения null и undefined

вот хорошее резюме: https://github.com/Microsoft/TypeScript/issues/185#issuecomment -71942237

@jbondc Я думаю, что недавно проверил спецификацию. void - это общий термин для null и undefined . Не знаю насчет void 0 , но я полагаю, что void тоже имеет это под своей крышей.

Выводит any . но null и undefined по-прежнему присваиваются всем типам.

void 0 всегда возвращает undefined также http://stackoverflow.com/a/7452352/449132.

@jbondc, если вопрос был о потенциальной системе типов с нулевым

function returnsNull() {
  return null;
}

должен вывести тип null .

Есть много угловых случаев вокруг undefined и, честно говоря, я (пока?) Не уверен в том, как лучше всего с этим справиться. Но прежде чем тратить много времени на размышления об этом, я хотел бы знать, согласится ли команда TS со стратегией подписки.

В прошлый раз, когда я принимал участие в этой дискуссии, она закончилась словами: «Мы не будем вносить критические изменения, и мы не хотим изменять значение исходного кода на основе внешних флагов / параметров». Идея, которую я описал выше, не является критическим изменением и относительно хороша с точки зрения интерпретации источника, хотя и не на 100%. Эта серая зона - вот почему я спрашиваю, что думает команда TS.

@ jods4 Я бы предпочел предполагаемый обнуляемый any , который сохранил бы существующий синтаксис. И предпочтительнее была бы стратегия согласия. [1]

Я бы предпочел явный синтаксис для типов, не допускающих значения NULL. Что касается объектов, это сломало бы слишком много вещей одновременно. Кроме того, необязательные аргументы всегда должны допускать значение NULL по очевидным причинам (и это должно быть невозможно изменить). Аргументы по умолчанию должны сохранять свою текущую подпись извне, но внутри самой функции аргумент может быть не обнуляемым.

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

[1] Введение флага по умолчанию не сработает, прежде всего, по практическим причинам. Просто просмотрите проблемы, с которыми люди столкнулись с --noImplicitAny , что привело к некоторым опасностям миграции, множеству практических проблем при проверке существования свойств, приводящих к --suppressImplicitAnyIndexErrors , и нескольким неработающим определениям DefinitoTyped.

@impinball

Я бы предпочел предполагаемый обнуляемый any , который сохранил бы существующий синтаксис. И предпочтительнее была бы стратегия согласия.

Я отредактировал свой ответ здесь. Если подумать об этом подробнее, я просто не вижу полезного случая, когда вы могли бы косвенно вывести только тип null . Например: () => null или let x = null или function y() { return null } .

Так что я согласен и дальше делать вывод о том, что any , вероятно, хорошо.

  1. Он обратно совместим.
  2. Все эти случаи в любом случае являются ошибками под noImplicitAny .
  3. Если у вас есть странный случай, когда вы хотите это сделать и это полезно для вас, вы можете явно объявить тип: (): null => null или let x: null = null или function y(): null { return null } .

Я бы предпочел явный синтаксис для типов, не допускающих значения NULL. Что касается объектов, это сломало бы слишком много вещей одновременно.

Другой вариант - сказать, что string самом деле означает string! | null . Следует отметить, что если вы прочитаете все обсуждения выше, это было сначала предложено, потому что была надежда, что он будет более совместим с существующим кодом (без изменения текущего значения string ). Но на самом деле был сделан вывод, что даже в этом случае огромное количество кода все равно будет взломано.

Учитывая, что принятие системы типов, не допускающих значения NULL, не будет тривиальным переключением для существующих кодовых баз, я бы выбрал вариант string? вместо string! потому что он кажется более чистым. Особенно если вы посмотрите исходный код компилятора TS, где почти все внутренние типы будут названы неточно :(

Было бы немного более осуществимым / неразрывным, чтобы сделать числа, которые не являются необязательными аргументами, не обнуляемыми по умолчанию с помощью флага компилятора. Это также потребует синтаксиса для явной пометки типов, допускающих значение NULL, но я уже вижу это как вероятный результат этого обсуждения.

Я думаю, что тут все становится запутанным. Для меня это кажется хорошим аргументом в пользу использования везде string? . Я бы не хотел смешивать number? и string! , это слишком быстро запутало бы.

[1] Введение флага по умолчанию не сработает, прежде всего, по практическим причинам.

Да, это не годится для существующей базы кода без изменений. Но:

  1. Он отлично подойдет для новых кодовых баз. С этим мы сможем построить лучшее будущее.
  2. Существующие кодовые базы получат _ некоторые_ преимущества и дополнительные проверки, даже если они никогда не соглашаются.
  3. Существующие кодовые базы могут выбирать для каждого файла, что позволяет новому коду быть более строгим и при желании может допускать постепенный переход к старому коду.

@ jods4

Я думаю, что тут все становится запутанным. Для меня это кажется хорошим аргументом в пользу использования строки? повсюду. Я бы не хотел, чтобы номер путали? и строка !, это слишком быстро запутало бы.

Я не имел в виду это в этом смысле. Я имел в виду, что для чисел, допускающих значение NULL, существует меньше вариантов использования, чем для строк, допускающих значение NULL. Во всяком случае, я не очень привязан к этому предложению (мне все равно).

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

Какими средствами? Я не верю, что в настоящее время вы можете настраивать компилятор для каждого файла. Я готов быть здесь, чтобы доказать, что ошибаюсь.

@impinball

Какими средствами? Я не верю, что в настоящее время вы можете настраивать компилятор для каждого файла. Я готов быть здесь, чтобы доказать, что ошибаюсь.

Может я ошибаюсь. Я никогда их не использовал, но, глядя на тестовые примеры, я вижу много комментариев, похожих на // <strong i="9">@module</strong> amd . Я всегда предполагал, что это способ указать параметры внутри ts файла. Но я никогда не пробовал это делать, так что, может быть, я совершенно ошибаюсь! Взгляните, например, здесь:
https://github.com/Microsoft/TypeScript/blob/master/tests/cases/conformance/externalModules/amdImportAsPrimaryExpression.ts

Если это не способ указать параметры для каждого файла, нам может понадобиться что-то новое. Обязательно указывать эту опцию для каждого файла, _ как минимум_ для .d.ts файлов. Может подойти специальный форматированный комментарий в верхней части файла, в конце концов, у нас уже есть поддержка /// <amd-dependency /> и т. Д.

РЕДАКТИРОВАТЬ : Я ошибаюсь. Изучение исходного кода показало мне, что эти вещи называются маркерами и предварительно анализируются бегуном FourSlash. Так что это только для тестов, а не внутри компилятора. Для этого нужно было придумать что-то новое.

В некоторых случаях также есть практические ограничения - это не очень практично для некоторых аргументов, таких как функции ES6, поскольку для этого потребуется полностью повторно проанализировать файл. И IIUC проверка типов выполняется в первую очередь на том же этапе, что и создание AST.

Если вы посмотрите на код парсера, комментарии /// <amd-dependency /> и /// <amd-module /> читаются до того, как что-либо еще будет проанализировано в файле. Можно добавить что-то вроде /// <non-null-types /> . Хорошо, это ужасное наименование, и я не за распространение произвольных «магических» вариантов, но это всего лишь идея.

@ jods4 Я думаю, что 'm pinball говорит, что IDE не будут поддерживать его без значительных изменений в том, как работает подсветка синтаксиса.

Будет ли в 2.0 какая-либо форма типа, не допускающего значения NULL?
В конце концов, машинописный текст привносит в javascript гораздо меньше вещей, если он не может гарантировать тип. Что мы можем получить после преодоления всех проблем и переписывания, вводимых машинописным текстом?
Просто лучше IntelliSense? Поскольку вам по-прежнему нужно проверять тип вручную или по коду в каждой функции, экспортируемой в другие модули.

Хорошие новости для всех, TypeScript 1.6 обладает достаточной выразительной способностью для моделирования и безопасного отслеживания отсутствующих значений (которые в противном случае кодируются значениями null и undefined ). По сути, следующий шаблон дает вам типы, не допускающие значения NULL:

declare module Nothing { export const enum Brand {} }
interface Nothing { 'a brand': Nothing.Brand }
export type Nullable<a> = a | Nothing;
var nothing : Nothing = null;
export function isNothing(value: Nullable<a>): value is Nothing {
    return value == null;
}
var something = Math.random() > 0.5 ? 'hey!' : nothing;
if (isNothing(something)) {
    // missing value
    // there is no way you can get anything out of it
    // there is also NO WAY to get a null reference exception out of it
    // because it doesn't have any methods or properties that could be examined
    // it is 100% explicit and typesafe to use
} else {
    // value is present, it is 100% GUARANTEED being NON-NULL
    // you just CANT get a null reference exception here either
    console.log(something.toLowerCase());
}

/** turns any unsafe values into safe ones */
export function sanitize<a>(unsafe: a) : Nullable<a> {
    return unsafe;
}

var safe = sanitize(toResultFromExternalCodeYouCannotTrust()); // <-- 100% safe to use

С учетом сказанного, запрос должен быть закрыт, потому что больше нет проблем. Дело закрыто, занятие прекращено.

@ aleksey-bykov

С учетом сказанного, запрос должен быть закрыт, потому что больше нет проблем.

Вы же не серьезно, не так ли? Мы неоднократно говорили вам, что шаблона для алгебраического типа, допускающего значение NULL, не было, потому что эта проблема была связана с этой проблемой.

Я _не_ собираюсь оборачивать каждый var x: number в моем коде в Nullable<number> или даже лучше NonNullable<x> . Это слишком много накладных расходов. Тем не менее, я хочу знать, что выполнение x *= 2 на 100% безопасно, где бы это ни происходило в моем коде.

Ваш код не решает проблему использования сторонней библиотеки. Я _не_ собираюсь вызывать sanitize в _все_ сторонние библиотеки или даже встроенные API DOM, которые я вызываю. Более того, я не хочу добавлять isNothing(safe) _все на место_. Я хочу иметь возможность вызвать let squares = [1,2,3].map(x => x*x) и быть на 100% уверенным во время компиляции, что squares.length безопасен. С _любым_ API, который я использую.

Точно так же мне нужна документация для сторонних JS-библиотек, в которых функция принимает null качестве входных данных или нет. Я хочу знать во время компиляции, в порядке ли $(element).css(null) или возникла ошибка.

Я хочу работать в командной среде, где я не могу гарантировать, что все будут последовательно использовать сложные шаблоны, такие как ваш. Ваш тип Nulllable абсолютно ничего не делает, чтобы помешать разработчику сделать let x: number = null; x.toString() (или что-то менее глупое, но с тем же эффектом).

И так далее. Этот билет _далее_ от закрытого, и проблема все еще существует на 100%.

Вы же не серьезно, не так ли?

Я очень серьезно.

Я не собираюсь закатывать все до единого ...

Почему нет? С синтаксисом ! или чем-то еще, что вы, ребята, продвигаете, вам все равно придется это сделать.

или даже лучше NonNullable

Он должен иметь значение Nullable. Мы моделируем типы, допускающие значение NULL, которые могут иметь значение, не допускающее значения NULL, или NULL, и это явно указано. В отличие от обычных типов, которые ВСЕГДА могут иметь значение или null и называться number подразумевая, что вы должны проверить его на null перед использованием.

Это слишком много накладных расходов.

Где?

Ваш код не решает проблему использования сторонней библиотеки.

Оно делает.

Я не собираюсь вызывать sanitize для каждой сторонней библиотеки или даже для встроенного DOM api, которые я вызываю.

Вы не обязаны. Все, что вам нужно сделать, это исправить файл определения этой сторонней библиотеки, заменив все, что может быть нулевым, на Nullable<*>

Точно так же мне нужна документация для сторонних JS-библиотек, в которых функция принимает значение null в качестве входных данных или нет.

То же самое. Определите этот метод как принимающий Nullable<string> вместо простого string .

Ваш тип Nulllable абсолютно ничего не делает, чтобы помешать разработчику выполнить let x: number = null; x.toString ()

На самом деле это не так. Вам нужно заблокировать ключевое слово null с помощью линтера.

Давай, мое решение на 95% работает и на 100% практично, и оно доступно СЕГОДНЯ, без необходимости принимать сложные решения и собирать всех на одной странице. Вопрос в том, что вам нужно: рабочее решение, которое выглядит не так, как вы ожидаете, но, тем не менее, работает, или получить его именно так, как вы хотите, со всем задним домкратом и вишнями наверху.

В 1.4 и 1.5 тип void не допускает никаких членов Object, включая подобные .toString (), поэтому type Nothing = void; должно быть достаточно вместо необходимости модуля (если это снова не изменится в 1.6). http://bit.ly/1OC5h8d

@ aleksey-bykov это не совсем так. Вы все равно должны быть осторожны, чтобы очистить все значения, которые могут быть нулевыми. Вы не получите предупреждения, если забудете это сделать. Это, безусловно, улучшение (меньше мест, где нужно быть осторожным).

Кроме того, чтобы проиллюстрировать, как с помощью хаков на самом деле вы далеко не уйдете, просто попробуйте

if (isNothing(something)) {
  console.log(something.toString())
}

Я не собираюсь закатывать все до единого ...

Почему нет? С участием ! синтаксис или что-то еще, что вы, ребята, продвигаете, вам все равно придется это сделать.

Хорошо, я _ могу_ сделать это, если все остальное будет решено. И если все будет решено, возможно, TS даже сочтет для этого немного языкового сахара.

или даже лучше NonNullable

Он должен иметь значение Nullable, то, что мы моделируем, являются типами, допускающими значение NULL, которые могут иметь значение, не допускающее значения NULL, или NULL, и это явно указано. В отличие от обычных типов, которые ВСЕГДА могут иметь значение или ноль и называться числом, подразумевая, что вы должны проверить его на нуль перед использованием.

Дело не в том, что я просто хочу избавиться от нулевых исключений. Я также хочу иметь возможность выражать типы, не допускающие значения NULL, со статической безопасностью.
Допустим, у вас есть метод, который всегда возвращает значение. Как и toString() всегда возвращает ненулевую строку.
Какие у вас есть варианты?
let x: string = a.toString();
Это нехорошо, потому что нет статической проверки безопасности x.length . В данном случае это так, но, как вы сказали, встроенный string вполне может быть null так что это _status quo_.
let x = sanitize(a.toString());
Хорошо, теперь я не могу использовать его без нулевой проверки, поэтому код _is_ безопасен. Но я не хочу добавлять if (isNothing(x)) везде, где я использую x в своем коде! Это и уродливо, и неэффективно, поскольку я очень хорошо знал, что x не имеет значения NULL во время компиляции. Выполнение (<string>x).length более эффективно, но по-прежнему чертовски уродливо делать это везде, где вы хотите использовать x .

Что я хочу сделать:

let x = a.toString(); // documented, non-null type string (string! if you want to)
x.length; // statically OK

Вы не можете добиться этого без надлежащей языковой поддержки, потому что все типы в JS (и TS 1.6) всегда допускают значение NULL.

Я хотел бы сказать, что программирование с необязательными (обнуляемыми) типами - это очень хорошая практика, насколько это возможно. Итак, то, что я здесь описываю, является важным сценарием, а не исключением.

Это слишком много накладных расходов.

Где?

См. Мой предыдущий ответ.

Ваш код не решает проблему использования сторонней библиотеки.

Оно делает.

Решена только половина проблемы. Предполагая, что определения библиотеки были обновлены, как вы предложили: каждый ввод или возвращаемое значение, допускающее значение NULL, заменяется на Nullable<X> вместо X.

Я все еще могу передать null функции, которая не принимает параметры null . Хорошо, этот факт теперь _документирован_ (что мы уже можем делать с комментариями JSDoc или чем-то еще), но я хочу, чтобы он был _принужден_ во время компиляции.
Пример: declare find<T>(list: T[], predicate: (T) => bool) . Оба параметра не должны быть нулевыми (я не использовал Nullable ), но могу сделать find(null, null) . Другая возможная ошибка заключается в том, что в объявлении говорится, что я должен вернуть ненулевое значение bool из предиката, но я могу сделать это: find([], () => null) .

Ваш тип Nulllable абсолютно ничего не делает, чтобы помешать разработчику выполнить let x: number = null; x.toString ()

На самом деле это не так. Вам нужно запретить ключевое слово null с помощью линтера.

Это и обеспечивая nothing глобальная переменная вместо не поможет с делами , которые я перечислил в точке прецедент, будет ли он?

С моей точки зрения это не 95%. Мне кажется, что синтаксис стал намного хуже для многих обычных вещей, и у меня до сих пор нет всей статической безопасности, которая мне нужна, когда я говорю о типах, не допускающих значения NULL.

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

Точно такая же работа по разделению, которую вам пришлось бы проделать в любом случае с гипотетическими типами, не допускающими значения NULL, о которых вы, ребята, говорите здесь. Вам нужно будет просмотреть все ваши файлы определений и указать ! там, где это необходимо. Чем это отличается?

Вы не получите предупреждения, если забудете это сделать.

То же самое. То же самое.

проиллюстрируйте, как с помощью хаков на самом деле вы далеко не уедете, просто попробуйте

Хотя вы правы в том, как я определил Ничего, что имеет toString из Object , но ... если мы возьмем идею @Arnavion (использования void для Nothing ) все вдруг щелкает

Смотреть

image

С моей точки зрения это не 95%. Мне кажется, что синтаксис стал намного хуже для многих обычных вещей, и у меня до сих пор нет всей статической безопасности, которая мне нужна, когда я говорю о типах, не допускающих значения NULL.

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

@ aleksey-bykov
Я вижу, что вы пытаетесь сделать, хотя мне кажется, что вы еще не разобрались со всеми случаями и, кажется, придумываете решения по ходу дела, когда кто-то указывает на дыру (например, замена null by void в последнем примере).

В конце концов _maybe_ вы заставите его работать так, как я хочу. Вы будете использовать сложный синтаксис для типов, допускающих значение NULL, вы заблокируете с помощью линтера любой источник null в программе, который в конечном итоге может сделать типы, допускающие значение NULL, ненулевыми. Попутно вам придется убеждать всех обновлять свои библиотеки, используя ваше нестандартное соглашение, иначе это просто бесполезно (заметьте, это верно для _ всех_ решений нулевой проблемы, а не только для вас).

В самом конце вам, возможно, удастся изменить систему типов, чтобы избежать встроенного типа null и привязать проверки, которые мы хотим, к вашему типу Nothing .

На данный момент, разве вы не чувствуете, что язык должен просто поддерживать это? Язык может реализовать все примерно так же, как вы хотите (внутренне). Но вдобавок ко всему у вас будет хороший синтаксис, сглаженные крайние случаи, отсутствие необходимости во внешних линтерах и, конечно же, лучшее принятие сторонними определениями. Разве это не имеет смысла?

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

Давай поговорим о том, что я думаю. Я думаю, что было бы неплохо проснуться в мире, где TypeScript завтра имеет типы, не допускающие значения NULL. Собственно говоря, с этой мыслью я ложусь спать каждую ночь. Но на следующий день этого не происходит, и я расстраиваюсь. Затем я иду на работу и снова и снова сталкиваюсь с одной и той же проблемой с нулями до недавнего времени, когда я решил найти шаблон, который может облегчить мою жизнь. Похоже, я нашел это. Я все еще хочу, чтобы в TypeScript не было значений NULL? Конечно, я делаю. Могу ли я жить без них и разочарований? Похоже, я могу.

Я все еще хочу, чтобы в TypeScript не было значений NULL? Конечно, я делаю.

Тогда почему вы хотите закрыть этот выпуск? : смайлик:

С учетом сказанного, запрос должен быть закрыт, потому что больше нет проблем. Дело закрыто, занятие прекращено.

Я рад, что вы нашли решение для своих нужд, но, как и вы, я все еще надеюсь, что однажды TS получит надлежащую поддержку. И я верю, что это еще может произойти (заметьте, не скоро). Команда C # в настоящее время проводит аналогичные мозговые штурмы, и, возможно, это поможет продвинуться вперед. Если C # 7 удается получить ненулевые типы (в чем еще нет уверенности), нет причин, по которым TS может не делать то же самое.

@ aleksey-bykov

Итак, чтобы упростить

var nothing: void = null;
function isNothing<a>(value: a | void): value is void {
    return value == null;
}
var something = Math.random() > 0.5 ? 'hey!' : nothing;

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

Это, по сути, обходной путь, который изначально делал меня немного счастливым, пока не отговорили полагаться на него .

Придется просмотреть все ваши файлы определений и поставить! где это уместно. Чем это отличается?

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

Я выступаю за " --noImplicitNull ". Это приведет к тому, что все типы по умолчанию будут иметь значение, не допускающее значения NULL, что немедленно даст обратную связь о потенциальных проблемах в любом существующем коде. Если есть "!", Он должен вести себя как Swift, чтобы средство проверки типов игнорировало возможность нулевых значений (небезопасное преобразование в непустое значение) - хотя это противоречит оператору ES7 + "конечная отправка" в обещаниях, поэтому он может быть не лучшим идея.

Если это кажется слишком радикальным с точки зрения нарушения обратной совместимости, это действительно моя вина. Я действительно должен попытаться найти время и попробовать его на вилке, чтобы доказать, что это не так радикально, как кажется. Фактически добавление "!" является более радикальным: потребовалось бы больше изменений, чтобы воспользоваться им, поскольку большинство значений на самом деле не являются нулевыми; --noImplicitNull является более постепенным, поскольку вы обнаружите больше ошибок, исправив определения типов, которые неправильно заявляют ненулевые значения, и без этих исправлений вы получите то же поведение, за исключением присвоения null, неинициализированных значений и забытых проверок для необязательных полей объекта .

Добавляя свой голос к обсуждению, мне нравится, как Котлин решил проблему, т.е.

  • собственные переменные по умолчанию не имеют типов, допускающих значение NULL, знак '?' меняет это
  • внешние переменные допускают значение NULL, знак '!!' отменяет
  • нулевая проверка - это приведение типа
  • внешний код можно проанализировать с помощью инструмента, генерирующего аннотации

Сравнивать:
1
2

  • если a|void забанен, можно переключиться на a|Nothing

Основное отличие при использовании стандартных, официальных языковых функций вместо хаков заключается в том, что сообщество (DefinentyTyped) помогает писать, тестировать и делиться файлами определений.

  • не понимаю, почему использование 100% легальных функций - это взлом
  • не переоценивайте навыки сообщества, многие (если не большинство) определений имеют низкое качество, и даже такие, как, скажем, jquery, довольно часто нельзя использовать в том виде, в котором они написаны, без некоторых изменений
  • опять же, шаблон никогда не будет так хорош, как полноценная функция, надеюсь, что каждый поймет, без сомнения
  • шаблон может решить ваши проблемы сегодня, тогда как функция может быть или не быть рядом
  • Если ситуацию можно эффективно (до 95%) решить, используя AB и C, зачем кому-то D делать то же самое? жаждете сахара? почему бы нам не сосредоточиться на том, что не может быть решено с помощью шаблона?

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

(Если вы не хотите полагаться на тип void, Nothing также может иметь значение class Nothing { private toString: any; /* other Object.prototype members */ } которое будет иметь такой же эффект. См. # 1108)

_Примечание: здесь ничего не говорится о сокращении синтаксиса. Пожалуйста, не бери это
как таковой._

Проще говоря: вот проблема с предложенным решением TS 1.6
здесь: это реально может работать, но это не относится к стандартной библиотеке или
большинство файлов определений. Это также не относится к математическим операторам. Если ты хочешь
чтобы пожаловаться на избыточный шаблон типа / времени выполнения, попробуйте сделать общий
коллекции работают на C ++ - это меркнет по сравнению, особенно если вы хотите
ленивая итерация или коллекции, которые не являются массивами (или, что еще хуже, попробуйте
объединяя два).

Проблема с решением, которое сейчас предлагает @ aleksey-bykov, заключается в том, что
это не применяется в ядре языка. Например, ты не можешь делать
Object.defineProperty(null, "name", desc) - вы получите TypeError от
целевой объект null . Другое дело: вы не можете присвоить какое-либо свойство объекту
null объект, как в var o = null; o.foo = 'foo'; . Вы получите
ReferenceError IIRC. Предлагаемое решение не может учитывать эти случаи.
Вот почему для этого нам нужна языковая поддержка.


_Теперь, немного мягкого велосипеда ..._

А что касается синтаксиса, мне нравится лаконичность строки «?» И
"! string" синтаксис, но что касается конечного результата, мне все равно, пока я
не писать ThisIsANullableType<T, U, SomeRidiculousAndUnnecessaryExtraGeneric<V, W>> .

В среду, 29 июля 2015 г., 16:10 Алексей Быков [email protected] написал:

  • если забанен | void, можно переключиться на | Nothing

    Основное отличие при использовании стандартных, официальных языковых функций
    вместо хаков - это то, что сообщество (ОпределенноТипед) готово помочь
    писать, тестировать и делиться файлами определений

    не понимаю, почему использование 100% легальных функций - это взлом

    не переоценивайте навыки сообщества, многие (если не большинство)
    определения там низкого качества, и даже такие, как, скажем, jquery, довольно

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

    опять же, шаблон никогда не будет так хорош, как полноценная функция, надеюсь

    все понимают, без сомнения

    шаблон может решить ваши проблемы сегодня, тогда как функция может или может

    не быть рядом

    если можно эффективно (до 95%) решить ситуацию с помощью АБ
    и C, зачем кому-то D делать то же самое? жаждете сахара? почему бы
    мы сосредоточены на том, что не может быть решено с помощью шаблона?

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

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -126082053
.

И могли бы мы хотя бы представить себе, как будет выглядеть конструкция навеса для велосипедов, прежде чем мы будем зацикливаться на том, в какой цвет его покрасить? Большинство из того, что я видел до сих пор после первых ~ 10 комментариев, это «Эй, мы хотим построить навес для велосипедов! В какой цвет мы должны его покрасить?» Ничего о том, чтобы конструкция была прочной. (См. № 3192 и первую половину № 1206 для пары других примеров этого - большая часть шума утихла, когда я наконец сделал серьезное предложение с логически созданным синтаксисом и полностью определенной семантикой.)

Имейте в виду: это, скорее всего, приведет к серьезному рефакторингу и частичному переписыванию определений типов стандартной библиотеки. Это также приведет к тому, что большая часть определений типов в DefininiteTyped потребуется переписать для первой версии TS, которая поддерживает это. Так что имейте в виду, что это определенно сломается. (Решение может заключаться в том, чтобы всегда генерировать сигнал по умолчанию, даже если возникают ошибки, связанные с нулевым значением, но для изменения этого поведения необходимо указать флаг а-ля --noImplicitAny .)

По общему признанию, я здесь немного выше головы. Тем не менее, я искал в scholar.google слова "javascript, допускающий значение NULL" и "javascript, допускающий значение NULL":
Понимание TypeScript
имеет красивую таблицу типов в TypeScript (универсальные модули).

Зависимые типы для JavaScript
кажется важным. исходный код исчез

Доверяйте, но проверяйте: двухэтапная типизация для динамическогоЯзыки
Типы уточнения для языков сценариев
предлагает решение на основе машинописного текста.
(«Сокращение t? Заменяет t + null.»; Звучит как # 186)

@afrische Многое из этого уже используется практически в средствах проверки типов JS. Например, Flow использует большинство идиом в слове «Доверяй, но проверяй». Также существует Infernu , средство проверки типов WIP JS, которое в значительной степени полагается на вывод типа 1 Хиндли-Милнера для определения своих типов. Но я отвлекся ...

[1] Haskell, OCaml и т. Д. Также используют модифицированную версию для своих систем типов.

В настоящее время я играю с вилкой TS, которая предполагает, что типы по умолчанию не допускают значения NULL, и что делает (существующий) тип Null ссылочным с помощью null , например string | null . Я также добавил синтаксис для string? который является просто ярлыком для того же типа объединения.

Если вдуматься, это то же самое, что и @ aleksey-bykov, но где null - это встроенный тип Nothing .

И это даже не сложно, потому что система типов уже неплохо справляется со всем этим!

Проблема в том, что мы хотим иметь плавный переходный путь. Нам нужна некоторая обратная совместимость, по крайней мере, с существующими файлами определений (совместимость с самим проектом может быть флагом выбора, хотя было бы лучше, если бы значение фрагмента кода - скажем, в Интернете - не изменялось. t зависят от глобальных флагов компилятора).

Идея @afrische использовать string? в вашем собственном проекте, но использовать string! в .d.ts может быть новым подходом. Хотя мне не очень нравится создаваемая им произвольная двойственность. Почему string допускает значение NULL, а некоторые файлы не допускают значения NULL в других? Кажется странным.

Если вы предпочитаете, чтобы в некоторых файлах не было строки, допускающей значение NULL, а в других - нет.
Мне нравится идея @impinball о наличии флага отказа в компиляторе и о введении его как критического изменения (вздох: большое изменение по

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

Я планирую перенести большую базу кода на Flow, Closure Compiler или TypeScript, но отсутствие нулевой безопасности в TypeScript является серьезным нарушением.

Не прочитав всю цепочку, я не могу понять, что не так с добавлением спецификаторов ! и ? допускающих значение NULL, при сохранении существующего поведения для типов, у которых они отсутствуют, так что здесь это как предложение :

Предложение

declare var foo:string // nullability unspecified
declare var foo:?string // nullable
declare var foo:!string // non-nullable

С ?string надмножеством !string и string как надмножеством, так и с подмножеством обоих, то есть отношения между string и обоими ?string и !string такое же, как отношение между any и всеми другими типами, то есть простой тип без ! или ? похож на any отношении недействительности.

Применяются следующие правила:

| Тип | Содержит | Предоставляет | Может назначить null ? | Может предположить не null ? |
| --- | --- | --- | --- | --- |
| T | T , !T , ?T | T , ?T , !T | да | да |
| ?T | T , !T , ?T | T , ?T | да | нет (требуется тип защиты) |
| !T | T , !T | T , ?T , !T | нет | да |

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

Пример

Вот код с ошибкой нулевой ссылки:

function test(foo:Foo) { foo.method(); }
test(null);

Этот код все еще проходит. null по-прежнему присваивается Foo а Foo прежнему можно считать ненулевым.

function test(foo:!Foo) { foo.method(); }
test(null);

Теперь test(null) является ошибочным, поскольку null не может быть присвоено !Foo .

function test(foo:?Foo) { foo.method(); }
test(null);

Теперь foo.bar() является ошибочным, поскольку вызов метода ?Foo запрещен (т.е. код должен проверять наличие null ).

Спасибо! И мне эта идея очень нравится. Хотя для совместимости
могли бы мы сделать ?T и T просто псевдонимами, например Array<T> и T[] ? Это
упростит ситуацию, а версия без украшений технически
обнуляемый.

Пт, 28 августа 2015 г., 07:14 Джесси Шалькен [email protected] написал:

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

Я планирую перенести большую базу кода на Flow, Closure Compiler или
TypeScript, но отсутствие безопасности типов в TypeScript является серьезным нарушением.

Не прочитав всю цепочку, я не могу понять, что случилось
с добавлением обоих! и ? спецификаторы допустимости значения NULL при сохранении
существующее поведение для типов, у которых они отсутствуют, поэтому вот это предложение:
Предложение

объявить var foo: string // неопределенное значение NULLdeclare var foo:? string // допускающее значение NULL, объявить var foo:! string // не допускающее значение NULL

С? String - это надмножество! String, а string - как надмножество, так и подмножество
обоих, то есть отношения между строкой и обоими? строка и! строка
такое же, как отношение между любым и всеми другими типами, т. е.
голый тип без! или ? похож на any в отношении недействительности.

Применяются следующие правила:
Тип Содержит Предоставляет Может ли присвоить значение null? Можете считать не нулевым? ТТ,! Т,? ТТ,
? T,! T да да? TT,! T,? TT,? T да нет (требуется тип охраны)! TT,! TT,
? T,! T нет да

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

Вот код с ошибкой нулевой ссылки:

функциональный тест (foo: Foo) {foo.method (); } тест (ноль);

Этот код все еще проходит. null по-прежнему присваивается Foo, а Foo все еще может
считается ненулевым.

функция test (foo:! Foo) {foo.method (); } тест (ноль);

Теперь проверка (null) ошибочна, так как null не может быть назначен! Foo.

функциональный тест (foo:? Foo) {foo.method (); } тест (ноль);

Теперь foo.bar () ошибается, поскольку вызов метода? Foo запрещен.
(т.е. код должен проверять на null).

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135743011
.

@impinball

Спасибо! И мне эта идея очень нравится. Хотя, для совместимости, можем ли мы сделать ?T и T просто псевдонимами, например Array<T> и T[] ? Это упростило бы ситуацию, а недекорированная версия все еще технически не имеет значения NULL.

_Всей идея_ заключается в том, что T , ?T и !T - это три разные вещи, и это _is_ ради совместимости (совместимости с существующим кодом TS). Я не знаю, как это объяснить лучше, извините. В смысле, я сделал маленький столик и все такое.

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

Пт, 28 августа 2015 г., 11:43 Джесси Шалькен [email protected] написал:

@impinball https://github.com/impinball

Спасибо! И мне эта идея очень нравится. Хотя для совместимости
могли бы мы сделать? T и T просто псевдонимами, например Arrayи т[]? Было бы
упростить вещи, и недекорированная версия по-прежнему технически не имеет значения NULL.

Вся идея в том, что T,? T и! T - три разные вещи,
и это _is_ для совместимости (совместимость с существующими TS
код). Я не знаю, как это объяснить лучше, извините. В смысле, я сделал
столик и все такое.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135810311
.

извините за то, что был умным задницей, но это не имеет смысла

?string надмножество !string , а string как надмножество, так и подмножество обоих

из теории множеств мы знаем, что множество A является подмножеством, а надмножество множества B является тогда и только тогда, когда A = B

если да, то когда вы говорите "из обоих", это может означать только все из? строки = все из строки и все из! строка = все из строки

так что в конечном итоге все-из-? строки = все-из-! строки

откуда все выходят, автобус никуда не едет, автобус не работает

@ aleksey-bykov Наверное, плохая формулировка. Я думаю, он имеет в виду, что string больше похоже на ?string | !string , принимая самые разрешительные ограничения.

Все это похоже по масштабу на все более распространенные в Java аннотации типов @Nullable и @NonNull / @NotNull , а также то, как они работают с неаннотированными типами.

И я определенно был бы за новый флаг для типов, которые неявно принимаются как не допускающие значения NULL, особенно примитивы.

Флаг «не допускающий обнуление по умолчанию» был бы неплохим, но он также нарушил бы большое количество определений Определенного Типа. Сюда входят определения Angular и Node, и для исправления этого потребуется много утомительной работы. Также может потребоваться обратный порт, чтобы новые типы по-прежнему обрабатывались не как синтаксические ошибки, но допустимость значений NULL не проверялась. Такой бэкпорт был бы единственным практическим способом смягчить поломку с помощью такого флага, особенно когда определения обновляются для типов, допускающих значение NULL. (Люди все еще используют TypeScript 1.4 в разработке, особенно в крупных проектах.)

@impinball

Люди по-прежнему используют TypeScript 1.4 в разработке, особенно в крупных проектах.

Я думаю, что история совместимости этой функции уже достаточно сложна, без попытки сделать новые определения совместимыми со старыми компиляторами (ненулевые типы FWIW почти тривиально добавить в текущий компилятор TS).

Если люди остаются на старых TS, им следует использовать старые определения. (Я знаю, что это непрактично).
Я имею в виду, что вещи все равно сломаются. Вскоре TS 1.6 добавит типы пересечений, и определения, которые используют его, также не будут совместимы.

Кстати, я удивлен, что люди остаются на 1.4, ведь в 1.5 есть что полюбить.

@ aleksey-bykov

извините за то, что был умным задницей, но это не имеет смысла

Я предполагаю, что вы называете себя «умной задницей», потому что из остальной части сообщения вы прекрасно знаете, что означает «надмножество и подмножество того и другого». Конечно, это бессмысленно применительно к реальным сетам.

any можно присвоить любой тип (он ведет себя как надмножество всех типов) и рассматривать как любой тип (он ведет себя как подмножество всех типов). any означает «отказаться от проверки типа для этого значения».

string может быть назначен как из !string и из ?string (он ведет себя как их надмножество) и может рассматриваться как !string и ?string (ведет себя как их подмножество). string означает «отказаться от проверки допустимости пустых значений для этого значения», то есть текущего поведения TS.

@impinball

И я определенно был бы за новый флаг для типов, которые неявно принимаются как не допускающие значения NULL, особенно примитивы.

@RyanCavanaugh прямо сказал:

Флаги, изменяющие семантику языка, опасны. [...] Важно, чтобы кто-то, смотрящий на фрагмент кода, мог «следовать» за системой типов и понимать сделанные выводы. Если у нас появится куча флагов, изменяющих правила языка, это станет невозможным.

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

Вот почему мое предложение поддерживает существующее поведение для типов, не имеющих спецификатора допустимости значения NULL ( ! или ? ). Добавляемый _only_ безопасный флаг будет тем, который запрещает типы, не имеющие спецификатора допустимости значения NULL, т.е. он принимает только передаваемый код и вызывает его ошибку, он не меняет его значения. Я предполагаю, что noImplicitAny было разрешено по той же причине.

@jesseschalken

Я имел в виду, что в контексте неявно типизированных переменных, не инициализированных
на null или undefined в таких случаях:

var a = new Type(); // type: !Type
var b = 2; // type: !number
var c = 'string'; // type: !string
// etc...

Извините за путаницу.

Пт, 28 августа 2015 г., 19:54 Джесси Шалькен [email protected] написал:

@impinball https://github.com/impinball

И я определенно был бы за новый флаг для типов, являющихся
неявно воспринимаются как не допускающие значения NULL, особенно примитивы.

@RyanCavanaugh https://github.com/RyanCavanaugh прямо сказал:

Флаги, изменяющие семантику языка, опасны. [...]
Важно, чтобы кто-то, глядя на фрагмент кода, мог «следовать за ним».
с системой типов и понять сделанные выводы. Если
у нас появляется куча флагов, которые меняют правила языка,
это становится невозможным.

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

Вот почему мое предложение поддерживает существующее поведение для типов, не имеющих
спецификатор допустимости значения NULL (! или?). _Only_ безопасный флаг, который нужно добавить, будет
тот, который запрещает типы без спецификатора допустимости значения NULL, т. е. только
принимает передаваемый код и вызывает его ошибку, это не меняет его значения. я
предположим, что noImplicitAny разрешено по той же причине.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135916676
.

@jeffmcaffer то, что вы пытаетесь сказать, изменяя исходное значение слов надмножество и подмножество, все еще можно легко сформулировать в терминах множеств: набор значений string представляет собой объединение значений !string и ?string означают что-либо из !string или / и ?string принадлежит string

@impinball Опять же, такой флаг изменил бы смысл существующего кода, что (из комментариев @RyanCavanaugh ) недопустимо. export var b = 5; теперь экспортирует !number туда, где раньше экспортировал number .

Да, в некотором смысле. Чтобы быть немного более техническим, он принимает союз, но
он обеспечивает пересечение. По сути, любой тип может считаться
string и string можно передать для любого типа.

Пт, 28.08.2015, 20:20 Алексей Быков [email protected] написал:

@impinball https://github.com/impinball то, что вы пытаетесь сказать
изменение первоначального значения слов надмножество и подмножество все еще может быть
легко формулируется в терминах множеств: множество значений строки - это
_union_ значений! string и? string, означающих что угодно, начиная с! string
или / и? строка принадлежит строке

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135920233
.

Очевидно, это не должно быть включено по умолчанию. И большинство файлов определений
не пострадает. Технически, любые изменения, которые не являются чисто
добавка (даже добавление нового аргумента к функции не в JavaScript) имеет
возможность взламывать приложения. По своему охвату он похож на
noImplicitAny в том смысле, что он требует более явного набора текста. И я
не верю, что он может сломать гораздо больше, тем более что единственный
способ, которым это может повлиять на другие файлы, - это экспорт из реального TypeScript
исходные файлы. (Другой флаг сломал множество файлов определений и отключил
частый способ тестирования уток.)

Пт, 28 августа 2015 г., 20:21 Джесси Шалькен [email protected] написал:

@impinball https://github.com/impinball Опять же, такой флаг изменится
смысл существующего кода, который (из чтения @RyanCavanaugh
https://github.com/RyanCavanaugh) не допускается. экспорт var
b = 5; теперь будет экспортировать! номер там, где раньше он экспортировал номер.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135920315
.

Очевидно, это не должно быть включено по умолчанию.

Это все еще меняет смысл кода. Как только вы это сделаете, вы не сможете прочитать какой-то код TypeScript и сказать: «Я знаю, что это значит», и вы не сможете написать библиотеку, которую будет использовать кто-то другой, потому что значение кода зависит от того, какие флаги используются для компилировать его, а это недопустимо. Вы буквально разделили язык на две части.

Вот почему мое предложение поддерживает существующее поведение для типов, не имеющих спецификатора допустимости значения NULL ( ? или ! ).

@jesseschalken
Если вы хотите (и вам удается) сохранить со 100% точностью значение существующего кода, то вам следует отказаться от идеи нулевой безопасности машинописного текста. Никакое решение не будет использоваться на практике.

Необходимость помещать ? и ! в аннотацию каждого типа уже достаточно многословна, чтобы я не хотел этого делать, но вам также придется отказаться от вывода всех типов. let x = 3 выводит number сегодня. Если вы не принимаете изменения здесь, это означает, что вам нужно явно ввести все, чтобы воспользоваться преимуществами нулевой безопасности. Я тоже не хочу этого делать.

Когда преимущества перевешивают недостатки, можно пойти на некоторые уступки. Как было указано в @impinball, команда TS сделала именно это с noImplicitAny . Это флаг, который создает новые ошибки в вашем коде, когда вы его включаете. Таким образом, копирование кода из Интернета или даже просто использование библиотек TS может сломаться, если код, который вы принимаете, не был написан с предположением noImplicitAny (и это случилось со мной).

Я думаю, что аналогичным образом можно ввести нулевую безопасность. Значение кода такое же, и он работает с точно такой же семантикой. Но под новым флагом (скажем, noImplicitNull или как-то еще) вы получите дополнительные проверки и предупреждения / ошибки из кода, который написан не с предположением noImplicitNull .

Я думаю, что аналогичным образом можно ввести нулевую безопасность. Значение кода такое же
и он работает с точно такой же семантикой. Но под новым флагом (скажем, noImplicitNull или что-то еще)
вы получите дополнительные проверки и предупреждения / ошибки из кода, написанного без
предположение noImplicitNull.

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

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

Так как насчет этого:

  • С -noImplicitNull T становится псевдонимом для !T . В противном случае обнуляемость T неизвестна.
  • Флаг должен быть переопределен для каждого модуля путем добавления аннотации вверху, например. <strong i="18">@ts</strong>:implicit_null . Это похоже на то, как Flow включает проверку типов для каждого модуля.
  • Преобразование существующей кодовой базы выполняется сначала путем добавления параметра компилятора -noImplicitNull, а затем аннотирования всех существующих модулей с помощью флага '
  • При импорте модуля должна быть политика о том, как преобразовывать типы, если импортируемые и импортируемые модули имеют разные настройки неявности.

Для этой последней точки существуют разные варианты, если модуль с явными значениями NULL импортирует модуль с неявными значениями NULL.

  • Крайний подход заключался бы в том, чтобы рассматривать возможность нулевого значения для типов T как неизвестную и требовать, чтобы импортер явно приводил тип к ?T или !T . Это потребует много аннотаций в вызываемом, но будет сафаром.
  • Другой подход - рассматривать все импортированные типы T как ?T . Для этого также потребуется много аннотаций в вызывающей стороне.
  • Наконец, все импортированные типы T можно рассматривать как !T . Конечно, в некоторых случаях это было бы неправильно, но, возможно, это наиболее прагматичный вариант. Подобно тому, как переменная типа any может быть присвоена значению типа T .

Мысли?

@ jods4 Флаг noImplicitAny _не_ меняет значение существующего кода, он только требует, чтобы код был явным в отношении чего-то, что в противном случае было бы неявным.

| Код | Флаг | Значение |
| --- | --- | --- |
| interface Foo { blah; } | | interface Foo { blah:any; } |
| interface Foo { blah; } | noImplicitAny | ошибка, требуется явный тип |
| var foo = 'blah' | | var foo:string = 'blah' |
| var foo = 'blah' | noImplicitNull | var foo:!string = 'blah' |

С noImplicitNull , раньше у вас была переменная, в которую можно было записать null. Теперь у вас есть переменная, в которую нельзя записать null _. Это совершенно другой зверь по сравнению с noImplicitAny .

@RyanCavanaugh уже исключил флаги, которые изменяют семантику существующего кода. Если вы собираетесь категорически игнорировать явные требования команды TS, то этот билет продлится еще год.

@jesseschalken Извините, но я не вижу разницы.
До noImplicitAny в вашем коде может быть следующее:
let double = x => x*2;
Он компилируется и отлично работает. Но как только вы включаете noImplicitAny , компилятор выдает вам ошибку, говоря, что x неявно равно any. Вы должны изменить свой код, чтобы он работал с новым флагом:
let double = (x: any) => x*2 или еще лучше let double = (x: number) => x*2 .
Обратите внимание, что хотя компилятор выдал ошибку, он все равно будет выдавать отлично работающий JS-код (если вы не отключите выдачу при ошибках).

На мой взгляд, ситуация с нулевыми значениями примерно такая же. Предположим для обсуждения, что с новым флагом T не допускает значения NULL, а T? или T | null обозначает объединение типа T и null .
Раньше вы могли иметь:
let foo: string; foo = null; или даже просто let foo = "X"; foo = null которые точно так же будут выведены из string .
Он компилируется и отлично работает. Теперь включите новый флаг noImplicitNull . Внезапно TS выдает ошибку, указывающую на то, что вы не можете назначить null чему-то, что не было явно объявлено как таковое. Но, за исключением опечатки, ваш код по-прежнему выдает тот же самый правильный JS-код.
С помощью флага вам нужно явно заявить о своем намерении и изменить код:
string? foo; foo = null;

Так в чем разница на самом деле? Код всегда издает нормально, и его поведение во время выполнения вообще не изменилось. В обоих случаях вы получаете ошибки от системы набора текста, и вам нужно изменить свой код, чтобы он был более явным в объявлениях типов, чтобы избавиться от них.

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

@robertknight Очень близко к моему нынешнему мышлению.

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

Проблема в том, что сегодня некоторые T фактически не допускают значения NULL, а некоторые допускают значение NULL. Учитывать:

// In a strict module, function len does not accept nulls
function len(x: string): number { return x.length; }
// In a legacy module, some calls to len
let abc: string = "abc";
len(abc);

Если вы укажете псевдоним string на string? в устаревшем модуле, тогда вызов станет ошибкой, потому что вы передадите возможно нулевую переменную в параметр, не допускающий значения NULL.

@ jods4 Прочтите мой комментарий еще раз. Посмотри на стол. Я не знаю, как это выразить яснее.

Ваш пример был специально создан, чтобы прийти к вашему выводу, поместив определение foo и присвоение foo рядом друг с другом. При использовании noImplicitAny единственные ошибки, которые возникают в результате, связаны именно с кодом, который необходимо изменить (поскольку он не изменил своего значения, он только потребовал его более явного выражения). С noImplicitNull код, вызвавший ошибку, был _assignment_ для foo но код, который необходимо было изменить, чтобы исправить это (чтобы иметь старое значение), был _definition_ foo . Это критически важно, потому что флаг _ изменил значение определения foo _. Назначение и определение, очевидно, могут находиться по разные стороны границы библиотеки, для которой флаг noImplicitNull изменил _значение_ публичного интерфейса этой библиотеки!

флаг изменил значение определения foo.

Да это правда. Он изменился с «Я не имею ни малейшего представления, может ли переменная содержать ноль или нет - и меня просто не волнует» на «Эта переменная не содержит нуль». Вероятность того, что это было правильным, составляет 50/50, а если это не так, вы должны указать свое намерение в заявлении. В конце концов, результат такой же, как и в случае с noImplicitAny : вы должны сделать свое намерение более явным в объявлении.

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

Действительно, обычно объявление в библиотеке и использование в моем коде. В настоящее время:

  1. Если библиотека выбрала строгие нули, она должна правильно объявлять свои типы. Если в библиотеке указано, что у нее есть строгие нулевые типы, а x не допускает значения NULL, то я пытаюсь присвоить значение NULL _, это действительно ошибка, о которой следует сообщить_.
  2. Если библиотека не выбрала строгие нули, компилятор не должен вызывать никаких ошибок при ее использовании.

Этот флаг (как и noImplicitAny ) нельзя включить без корректировки кода.

Я понимаю вашу точку зрения, но я бы сказал, что мы не _изменяем_ смысловой код; скорее, мы _выражаем то, что сегодня не предусмотрено_ системой типов.

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

Хорошие новости для типов, не допускающих значения NULL! Похоже, команда TS в порядке с внесением критических изменений в обновления TS!
Если вы видите это:
http://blogs.msdn.com/b/typescript/archive/2015/09/02/announcing-typescript-1-6-beta-react-jsx-better-error-checking-and-more.aspx
Они вводят критическое изменение (взаимоисключающий необязательный синтаксис) с новым типом файла, и они вводят критическое изменение _без_ нового типа файла (затрагивает всех).
Это прецедент, с которым мы можем спорить о типах, не допускающих значения NULL (например, расширение .sts или строгое TypeScript и соответствующие .sdts).

Теперь нам просто нужно выяснить, хотим ли мы, чтобы компилятор пытался проверять неопределенные типы или только нулевые типы (и какой синтаксис), и у нас есть твердое предложение.

@jbondc Очень интересно читать. Рад видеть, что моя интуиция о том, что миграция на NNBD (по умолчанию не допускающая значения NULL) проще, чем миграция на необязательный параметр, не допускающий значения NULL, была подтверждена исследованиями (на порядок меньше изменений для миграции, а в случае с Dart: 1- 2 аннотации на 1000 строк кода требовали изменения нулевого значения, не более 10 даже в коде с большим количеством нулей)

Я не уверен, действительно ли сложность документа отражает сложность типов, не допускающих значения NULL. Например, в разделе универсальных типов они обсуждают 3 типа параметров формального типа, а затем показывают, что они вам на самом деле не нужны. В TS null будет просто типом полностью пустой записи (без свойств, без методов), в то время как {} будет корневым ненулевым типом, а затем ненулевые универсальные шаблоны будут просто G<T extends {}> - вообще не нужно обсуждать несколько типов параметров формального типа.

Вдобавок кажется, что они предлагают много несущественного сахара, например var !x

Однако настоящая жемчужина - обзор существующих языков, в которых решалась та же проблема.

Читая документ, я понял, что типы Optional / Maybe более мощные, чем типы, допускающие значение NULL, особенно в общем контексте - в основном из-за возможности кодировать Just(Nothing) . Например, если у нас есть общий интерфейс Map, который содержит значения типа T и поддерживает get который может возвращать или не возвращать значение в зависимости от наличия ключа:

interface Map<T> {
  get(s:string):Maybe<T>
}

ничто не мешает T быть типа Maybe<U> ; код будет работать отлично и вернет Just(Nothing) если ключ присутствует, но содержит значение Nothing , и просто вернет Nothing если ключ отсутствует вообще.

Напротив, если мы используем типы, допускающие значение NULL

interface Map<T> {
  get(s:string):T?
}

тогда невозможно отличить отсутствующий ключ от нулевого значения, когда T допускает значение NULL.

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

@jbondc Это очень

Меня утешает то, что исследования показывают, что 80% объявлений на самом деле означают ненулевое значение или что на KLOC имеется только 20 аннотаций об отсутствии значения (стр. 21). Как отмечалось в документе, это веский аргумент в пользу ненулевого значения по умолчанию, что также было моим ощущением.

Еще один аргумент в пользу ненулевого значения заключается в том, что он создает более чистую систему типов: null - это собственный тип, T является нулевым, а T? является синонимом T | null . Поскольку TS уже имеет тип объединения, все красиво, чисто и хорошо работает.

Увидев список последних языков, которые решили эту проблему, я действительно думаю, что новый современный язык программирования должен решить эту давнюю проблему и уменьшить количество нулевых ошибок в кодовых базах. Это слишком распространенная проблема для чего-то, что следует моделировать в системе типов. Я все еще надеюсь, что TS когда-нибудь его получит.

Я нашел идею оператора T! интригующей и, возможно, полезной. Я думал о системе, в которой T - это ненулевой тип, T? - это T | null . Но меня беспокоило, что вы не можете создать общий API, который гарантирует ненулевой результат даже при нулевом вводе. У меня нет хороших сценариев использования, но теоретически я не смог бы точно смоделировать это: function defined(x) { return x || false; } .
Используя ненулевой оператор разворота, можно сделать: function defined<T>(x: T): T! | boolean . Это означает, что если defined возвращает T оно гарантированно будет ненулевым, даже если общее ограничение T допускало значение NULL, скажем, string? . И я не думаю, что это сложно моделировать в TS: учитывая T! , если T - это тип объединения, который включает в себя тип null , вернуть тип, полученный в результате удаления null от союза.

@spion

Читая документ, я понял, что типы Optional / Maybe более мощные, чем типы, допускающие значение NULL.

Вы можете вложить Maybe структуры, но не можете вложить null .

Это интересное обсуждение в контексте определения новых API-интерфейсов, но при отображении существующих API-интерфейсов выбора мало. Если преобразовать языковую карту в Nulls в Maybe это преимущество не будет реализовано, если функция не будет полностью переписана.

Maybe кодирует две отдельные части информации: есть ли значение и каково это значение. Если взять ваш пример Map и посмотреть на C #, это очевидно, из Dictionary<T,K> :
bool TryGet(K key, out T value) .
Обратите внимание, что если в C # есть кортежи (возможно, в C # 7), это в основном то же самое, что:
(bool hasKey, T value) TryGet(K key)
Это в основном Maybe и позволяет хранить null .

Обратите внимание, что JS имеет свой собственный способ решения этой проблемы и создает множество новых интересных проблем: undefined . Типичный JS Map вернет undefined если ключ не найден, в противном случае - его значение, включая null .

Связанное предложение для C # 7 - https://github.com/dotnet/roslyn/issues/5032

Вы, ребята, понимаете, что проблема не решена, если вы не смоделируете undefined таким же образом?
В противном случае все ваши нулевые проблемы будут просто заменены неопределенными проблемами (которые в любом случае более распространены).

@Griffork

все ваши нулевые проблемы будут заменены неопределенными проблемами

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

Правда, undefined все еще проблема. Но это во многом зависит от вашего стиля кодирования. Я кодирую почти без undefined за исключением тех случаев, когда браузер навязывает их мне, что означает, что 90% моего кода будет безопаснее с нулевыми проверками.

Я кодирую почти без undefined, кроме тех случаев, когда браузер навязывает их мне

Я бы подумал, что JavaScript заставляет undefined на каждом шаге.

  • Неинициализированные переменные. let x; alert(x);
  • Пропущенные аргументы функции. let foo = (a?) => alert(a); foo();
  • Доступ к несуществующим элементам массива. let x = []; alert(x[0]);
  • Доступ к несуществующим свойствам объекта. let x = {}; alert(x['foo']);

Null, с другой стороны, встречается в меньшем количестве и в более предсказуемых ситуациях:

  • Доступ к DOM. alert(document.getElementById('nonExistent'));
  • Ответы сторонних веб-сервисов (поскольку JSON.stringify strips undefined ). { name: "Joe", address: null }
  • Регулярное выражение.

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

Следовательно, я согласен с тем, что проблема undefined является более распространенной.

@NoelAbrahams Стиль кодирования, говорю вам :)

Неинициализированные переменные

Я всегда инициализирую переменные, и у меня включен noImplicitAny , поэтому let x в любом случае будет ошибкой. В своем проекте я бы использовал более близкое значение let x: any = null , хотя этот код я бы писал нечасто.

Необязательные параметры функции

Я использую значения параметров по умолчанию для необязательных параметров, мне кажется, что это имеет больше смысла (ваш код _will__ как-то читает и использует параметр, не так ли?). Итак, для меня: function f(name?: string = null, options?: any = {}) .
Доступ к необработанному значению параметра undefined был бы для меня _исключительным_ случаем.

Доступ к несуществующим элементам массива

Это то, чего я стараюсь не делать в своем коде. Я проверяю длину своих массивов, чтобы не выходить за границы, и я не использую разреженные массивы (или пытаюсь заполнить пустые слоты значениями по умолчанию, такими как null , 0 , ...).
Опять же, вы можете придумать особый случай, когда я бы это сделал, но это будет _exception_, а не правило.

Доступ к несуществующим свойствам объекта.

Практически то же самое, что и для массивов. Мои объекты набраны, если значение недоступно, я устанавливаю его на null , а не на undefined . Опять же, вы можете найти крайние случаи (например, пробуждение по словарю), но они _ крайние случаи_ и не репрезентативны для моего стиля кодирования.

Во всех _исключительных_ случаях, когда я получаю возврат undefined , я немедленно принимаю меры в отношении результата undefined и не распространяю его дальше и не "работаю" с ним. Типичный пример из реальной жизни в вымышленном компиляторе TS с нулевыми проверками:

let cats: Cat[];
// Note that find returns undefined if there's no cat named Kitty
let myFavoriteCat = cats.find(c => c.name === 'Kitty'); 
if (myFavoriteCat === undefined) {
  // Immediately do something to compensate here:
  // return false; or 
  // myFavoriteCat = new Cat('Kitty'); or
  // whatever makes sense.
}
// Continue with assurance that myFavoriteCat is not null (it was an array of non-nullable cats after all).

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

Из этого я понимаю, что вы используете совсем другой стиль кодирования, чем я. Если вы в основном используете undefined везде, тогда да, вы выиграете от статически проверенных нулевых типов намного меньше, чем я
было бы.

Да, эта штука не на 100% водонепроницаема из-за undefined и я не верю, что можно создать разумно используемый язык, который был бы на 100% правильным в этом отношении. JS слишком часто вводит undefined .

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

@ jods4 , интересно было прочитать ваш подход.

Я думаю, что возражение, которое я имею против такого подхода, заключается в том, что, похоже, существует множество правил, которых необходимо придерживаться - это скорее похоже на коммунизм против свободного рынка: smile:

У команды TS есть внутреннее

@NoelAbrahams «Использовать undefined » - такое же правило, как и «Использовать null ».

В любом случае согласованность является ключевым моментом, и я бы не хотел проект, в котором я не уверен, что должно быть null или undefined (или пустая строка или ноль). Тем более что TS на данный момент не помогает с этим вопросом ...

Я знаю, что в TS есть правило отдавать предпочтение undefined сравнению с null , мне любопытно, является ли это произвольным выбором «ради согласованности» или есть больше аргументов в пользу выбора.

Почему мне нравится использовать null а не undefined :

  1. Это работает знакомо для наших разработчиков, многие из них пришли из статических языков объектно-ориентированного программирования, таких как C #.
  2. Неинициализированные переменные обычно рассматриваются как запах кода на многих языках, и я не знаю, почему он должен быть другим в JS. Сделайте свое намерение ясным.
  3. Хотя JS - это динамический язык, производительность лучше со статическими типами, которые не меняются. Более эффективно установить для свойства значение null чем delete it.
  4. Он поддерживает чистую разницу между null который определен, но означает отсутствие значения, и undefined , который ... не определен. Место, где разница между ними очевидна: необязательные параметры. Отсутствие передачи параметра приводит к undefined . Как вы _пропускаете_ пустое значение, если вы используете для этого undefined в своей базе кода? Используя null здесь нет проблем.
  5. Он прокладывает чистый путь к нулевой проверке, как обсуждалось в этом потоке, что на самом деле нецелесообразно с undefined . Хотя, возможно, я мечтаю об этом.
  6. Вы должны сделать выбор по согласованности, IMHO null ничем не хуже undefined .

Я думаю, что причина предпочтения undefined null заключается в том, что
аргументы по умолчанию и согласованность с возвратом obj.nonexistentProp
undefined .

Кроме этого, я не получаю скидку за то, что считается нулевым.
достаточно, чтобы потребовать, чтобы переменная допускала значение NULL.

Во вторник, 8 сентября 2015 г., 06:48 jods [email protected] написал:

@NoelAbrahams https://github.com/NoelAbrahams «Использовать undefined» означает
такое правило, как «Использовать ноль».

В любом случае последовательность является ключевым моментом, и я бы не хотел проект, в котором я работаю.
не уверен, должно ли что-то быть нулевым или неопределенным (или пустым
строка или ноль). Тем более что TS на данный момент в этом не помогает
проблема...

Я знаю, что TS имеет правило отдавать предпочтение undefined над null, мне любопытно, если это
является произвольным выбором "для единообразия" или если их больше
аргументы в пользу выбора.

Почему _I_ нравится использовать null, а не undefined:

  1. Для наших разработчиков это работает знакомо, многие пришли из статического объектно-ориентированного проектирования.
    такие языки, как C #.
  2. Неинициализированные переменные обычно рассматриваются как запах кода в
    много языков, не знаю, почему в JS должно быть по-другому. Сделайте свой
    намерение ясно.
  3. Хотя JS - динамический язык, производительность лучше с
    статические типы, которые не меняются. Более эффективно установить свойство на
    null, чем удалить его.
  4. Он поддерживает чистую разницу между null, который определен, но
    означает отсутствие значения, а undefined, что ... undefined.
    Место, где разница между ними очевидна: необязательно
    параметры. Отсутствие передачи параметра приводит к неопределенному результату. Как ты
    _pass_ пустое значение, если вы используете undefined для этого в своем коде
    база? Здесь нет проблем с использованием null.
  5. Он прокладывает чистый путь к нулевой проверке, как обсуждается в этом
    thread, что непрактично с undefined. Хотя может быть
    Я мечтаю об этом.
  6. Вы должны сделать выбор по согласованности, IMHO null так же хорош, как
    неопределенный.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -138514437
.

@impinball
Можем ли мы перестать использовать «байкшеддинг» в каждом обсуждении на github? Мы можем с уверенностью сказать, что использование undefined или null является предпочтением команды; но вопрос, пытаться ли включать undefined в нулевые проверки (или нет) и как это будет работать, нетривиален. Так что я вообще не понимаю, как это происходит?

Я создал форк TS 1.5 с типами, не допускающими значения NULL, и это оказалось на удивление легко. Но я думаю, что есть две сложные проблемы, по которым требуется консенсус, чтобы иметь типы, не допускающие значения NULL, в официальном компиляторе TS, оба подробно обсуждались выше без четкого вывода:

  1. Что нам делать с undefined ? (мое мнение: он еще везде и не отмечен)
  2. Как обеспечить совместимость с существующим кодом, в частности с определениями? (Мое мнение: флаг согласия, по крайней мере, для файла определения. Включение флага "нарушение", потому что вам, возможно, придется добавить нулевые аннотации.)

Мое личное мнение таково, что null и undefined следует рассматривать
эквивалентно для целей обнуления. Оба используются для этого варианта использования,
представляющий отсутствие значения. Во-первых, ценности никогда не существовало,
а во-вторых, ценность когда-то существовала и больше не существует. Оба
должен считаться допускающим значение NULL. Большинство функций JS возвращают undefined, но многие
DOM и библиотечные функции возвращают значение null. Оба служат для одного и того же варианта использования. Таким образом,
к ним следует относиться одинаково.

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

Во вторник, 8 сентября 2015 г., 13:28 jods [email protected] написал:

@impinball https://github.com/impinball
Можем ли мы перестать использовать «байкшеддинг» в каждом обсуждении на github? Мы можем безопасно
скажите, что использование undefined или null - это предпочтение команды; но проблема
стоит ли пытаться включать undefined в нулевые проверки (или нет) и как
это бы сработало нетривиально. Так что я не понимаю, как это
первое место?

Я построил вилку TS 1.5 с типами, не допускающими значения NULL, и это было
на удивление легко. Но я думаю, что есть два трудных вопроса, которые
нужен консенсус, чтобы в официальном компиляторе TS были типы, не допускающие значения NULL,
оба были подробно обсуждены выше без четкого вывода:

  1. Что нам делать с undefined? (моё мнение: всё ещё везде
    и не отмечен)
  2. Как мы обеспечиваем совместимость с существующим кодом, в частности
    определения? (мое мнение: флаг отказа, по крайней мере, для файла определения.
    Включение флага "нарушение", потому что вам, возможно, придется добавить ноль
    аннотации.)

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -138641395
.

@ jods4 Я понимаю из вашего пункта 2, что ваша вилка также предполагает тип, не допускающий значения NULL, по умолчанию вместо того, чтобы требовать явной аннотации типа, не допускающего значения NULL?

Можете связать, пожалуйста? Я бы хотел попробовать.

@impinball
Мне бы хотелось увидеть (некоторую) защиту и от undefined , но она довольно распространена.

В частности, можем ли мы определить массив типов, не допускающих значения NULL?
Учитывая, что доступ к массиву, выходящему за границы (или разреженный), возвращает undefined , можем ли мы удобно определять и использовать массивы?
Я думаю, что требование обнуления всех массивов на практике является слишком большим бременем.

Следует ли различать типы null и undefined ? Это несложно: T | null , T | undefined , T | null | undefined и может дать простой ответ на вопрос выше. Но как насчет сокращенного синтаксиса: что означает T? ? Оба значения - null и undefined? Нужны ли нам два разных сокращения?

@Arnavion
Типы Null и Undefined уже существуют в TS.
Я решил:

  1. Сделать все типы не допускающими значения NULL (включая предполагаемые типы);
  2. Дайте нулевому типу имя ( null ), которое вы можете использовать в объявлениях типов;
  3. Удалите расширение с null до any ;
  4. Введите сокращение синтаксиса T? которое совпадает с T | null ;
  5. Удалите неявные преобразования из null в любой другой тип.

Не имея доступа к своим источникам, я думаю, что в этом суть. Существующий тип Null и замечательная система типов TS (особенно типы соединений и защиты типов) делают все остальное.

Я еще не завершил свою работу над github, поэтому пока не могу поделиться ссылкой. Сначала я хотел закодировать переключатель совместимости, но был очень занят другими вещами :(
Переключатель совместимости гораздо сложнее <_ <. Но это важно, потому что прямо сейчас компилятор TS компилируется с множеством ошибок, а многие существующие тесты терпят неудачу.
Но, похоже, на самом деле он отлично работает с новым кодом.

Позвольте мне резюмировать то, что я видел от некоторых комментаторов, в надежде, что я смогу проиллюстрировать, как этот разговор идет по кругу:
Проблема: некоторые значения «особого случая» находят свое было в коде, который не предназначен для работы с ними, потому что они обрабатываются иначе, чем любой другой тип в языке (например, null и undefined).
Я: почему бы вам просто не изложить стандарты программирования, чтобы этих проблем не возникало?
Другое: потому что было бы хорошо, если бы намерение могло быть отражено в типизации, потому что тогда это не будет документироваться по-разному каждой командой, работающей над типовым скриптом, и будет меньше ложных предположений о сторонних библиотеках.
Everone: Как мы собираемся решать эту проблему?
Другое: давайте сделаем нули более строгими и воспользуемся стандартами для работы с undefined!

Я не понимаю, как это можно считать решением, если undefined - гораздо большая проблема, чем null!

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

Блин, телефон!
*все

* Достаточно вспомнить, что я хотел это выделить.

Также последний абзац следует читать «только так это предложение».

@ jods4 Я бы сказал, что это будет зависеть от определенных конструкций. В некоторых случаях вы можете гарантировать недопустимость обнуления в таких случаях, например, в следующих случаях:

declare const list: T![];

for (const entry of list) {
  // `entry` is clearly immutable here.
}

list.forEach(entry => {
  // `entry` is clearly immutable here.
})

list.map(entry => {
  // `entry` is clearly immutable here.
})

В этом случае у компилятора должна быть тонна логики, чтобы гарантировать, что массив проверяется в границах:

declare const list: T![]

for (let i = 0; i < list.length; i++) {
  // This could potentially fail if the compiler doesn't correctly do the static bounds check.
  const entry: T![] = list[i];
}

И в этом случае вы не можете гарантировать, что компилятор может проверить доступ для получения entry находится в пределах без фактической оценки частей кода:

declare const list: T![]

const end = round(max, list.length);

for (let i = 0; i < end; i++) {
  const entry: T![] = list[i];
}

Есть простые и очевидные из них, но есть и более сложные.

@impinball Действительно, современные API, такие как map , forEach или for..of подходят, потому что они пропускают элементы, которые никогда не были инициализированы или удалены. (Они действительно включают элементы, для которых установлено значение undefined , но наш гипотетический нулевой безопасный TS запретит это.)

Но классический доступ к массивам - важный сценарий, и я хотел бы найти для этого хорошее решение. Очевидно, что выполнение сложного анализа, как вы предложили, невозможно, за исключением тривиальных случаев (но важных случаев, поскольку они распространены). Но обратите внимание, что даже если вы могли бы доказать, что i < array.length это не доказывает, что элемент инициализирован.

Рассмотрим следующий пример. Как вы думаете, что должна делать TS?

let array: T![] = [];  // an empty array of non-null, non-undefined T
// blah blah
array[4] = new T();  // Fine for array[4], but it means array[0..3] are undefined, is that OK?
// blah blah
let i = 2;
// Note that we could have an array bounds guard
if (i < array.length) {
  let t = array[i];  // Inferred type would be T!, but this is actually undefined :(
}

Также есть проблема с Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
  get() { return 10 },
})

В среду, 9 сентября 2015 г., 17:49 jods [email protected] написал:

@impinball https://github.com/impinball Действительно, современный API, такой как карта,
forEach или for..of подходят, потому что они пропускают элементы, которые никогда не были
инициализировано или удалено. (Они включают элементы, для которых установлено значение
undefined, но наш гипотетический нулевой безопасный TS запретит это.)

Но классический доступ к массиву - важный сценарий, и я хотел бы
найти хорошее решение для этого. Выполнение комплексного анализа, как вы предложили, - это
очевидно, невозможно, за исключением тривиальных случаев (но важных случаев, поскольку
они обычные). Но учтите, что даже если бы вы смогли доказать, что i <
array.length не доказывает, что элемент инициализирован.

Рассмотрим следующий пример. Как вы думаете, что должна делать TS?

пусть массив: T! [] = []; // пустой массив ненулевых, не неопределенных T // бла-бла
массив [4] = новый T (); // Прекрасно для array [4], но это означает, что array [0..3] не определены, это нормально? // blah blahlet i = 2; // Обратите внимание, что мы могли бы иметь границы массива guardif (i <array. длина) {
пусть t = array [i]; // Предполагаемый тип - T !, но на самом деле он не определен :(
}

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139055786
.

Все, что вам нужно сделать, это:

  1. Сделайте ссылки на типы Null и Undefined.
  2. Предотвратить присвоение значений null и undefined чему-либо.
  3. Используйте тип объединения с Null и / или Undefined, если подразумевается допустимость значений NULL.

Это действительно серьезное изменение, которое больше подходит для языка.
расширение.
10 сентября 2015 г. в 9:13 "Исайя Медоуз" [email protected] написал:

Также есть проблема с Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
get() { return 10 },
})

В среду, 9 сентября 2015 г., 17:49 jods [email protected] написал:

@impinball https://github.com/impinball Действительно, современные API, такие как
карта,
forEach или for..of подходят, потому что они пропускают элементы, которые никогда не были
инициализировано или удалено. (Они включают элементы, для которых установлено значение
undefined, но наш гипотетический нулевой безопасный TS запретит это.)

Но классический доступ к массиву - важный сценарий, и я хотел бы
найти хорошее решение для этого. Выполнение комплексного анализа, как вы предложили, - это
очевидно, невозможно, за исключением тривиальных случаев (но важных случаев, поскольку
они обычные). Но учтите, что даже если бы вы смогли доказать, что i <
array.length не доказывает, что элемент инициализирован.

Рассмотрим следующий пример. Как вы думаете, что должна делать TS?

пусть массив: T! [] = []; // пустой массив ненулевых, не неопределенных T //
бла бла
массив [4] = новый T (); // Хорошо для массива [4], но это означает, что массив [0..3]
undefined, это нормально? // blah blahlet i = 2; // Обратите внимание, что у нас может быть
границы массива guardif (i <array.length) {
пусть t = array [i]; // Предполагаемый тип - T !, но на самом деле это
неопределенный :(
}

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139055786>
.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139230568
.

@impinball
Я нормально отношусь к твоему примеру. Использование defineProperty таким образом - это выход за пределы коробки безопасности TS в область динамического JS, вам не кажется? Не думаю, что я когда-либо называл defineProperty прямо в коде TS.

@ aleksey-bykov

выглядит более подходящим для языкового расширения.

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

Я знаю, что у типизированных массивов есть гарантия, потому что IIRC выдают
ReferenceError при загрузке и сохранении массива вне границ. Обычные массивы и
Объекты arguments возвращают undefined, когда индекс выходит за пределы. В моем
считают, что это недостаток языка JS, но его исправление определенно
сломать Интернет.

Единственный способ "исправить" это в ES6, через конструктор, возвращающий прокси,
и его прототип экземпляра и собственный прототип установлен в исходный массив
конструктор. Что-то вроде этого:

Array = (function (A) {
  "use strict";
  function check(target, prop) {
    const i = +prop;
    if (prop != i) return target[prop];
    if (i >= target.length) {
      throw new ReferenceError();
    }
    return i;
  }

  function Array(...args) {
    return new Proxy(new Array(...args), {
      get(target, prop) {
        return target[check(target, prop)];
      },
      set(target, prop, value) {
        return target[check(target, prop)] = value;
      },
    });
  }

  Array.prototype = A.prototype;
  Array.prototype.constructor = Array
  Object.setPrototypeOf(Array, A);
  return Array;
})(Array);

(примечание: это не проверено, набрано на телефоне ...)

10 сентября 2015 г., 10:09 jods [email protected] написал:

@impinball https://github.com/impinball
Я нормально отношусь к твоему примеру. Таким образом использовать defineProperty - это
выходя за пределы коробки безопасности TS и попадая в сферу динамического JS, не
ты думаешь? Не думаю, что когда-либо вызывал defineProperty непосредственно в коде TS.

@ aleksey-bykov https://github.com/aleksey-bykov

выглядит более подходящим для языкового расширения.

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

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139245706
.

@impinball Не уверен, что есть что "исправить" в первую очередь ...
Это семантика JS, и TS должен как-то их учитывать.

Что, если у нас есть (немного другая) разметка для объявления разреженного массива по сравнению с не разреженным массивом, и мы сделаем не разреженный массив автоматически инициализированным (возможно, программисты могут предоставить значение или уравнение для инициализации). Таким образом, мы можем заставить разреженные массивы иметь тип T|undefined (который изменится на тип T с использованием for ... of и других «безопасных» операций) и оставить типы неразреженных массивов в покое.

//not-sparse
var array = [arrlength] => index*3;
var array = <number[]>[3];
//sparse
var array = [];

Очевидно, это не окончательный синтаксис.
Во втором примере каждое значение инициализируется компилятором по умолчанию для этого типа.
Это означает, что для не разреженных массивов вы должны ввести их, иначе я подозреваю, что вам придется привести их к чему-то после того, как они будут полностью инициализированы.
Также нам нужен не разреженный тип для массивов, чтобы программисты могли преобразовать свои массивы в неразреженный.

@Griffork
Я не знаю ... это сбивает с толку.

Таким образом, мы можем заставить разреженные массивы иметь тип T|undefined (который изменится на тип T, используя for ... of и другие «безопасные» операции)

Из-за причуды в JS это не работает. Предположим, let arr: (T|undefined)[] .
Так что я могу сделать: arr[0] = undefined .
Если я это сделаю, то использование этих «безопасных» функций _will_ вернет undefined для первого слота. Итак, в arr.forEach(x => ...) нельзя сказать, что x: T . Это все еще должно быть x: T|undefined .

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

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

TS, очевидно, не может защитить вас от всего, что есть в JS, и есть несколько функций / конструкций, которые вы можете вызывать из действительного TS, которые имеют динамическое влияние на тип времени выполнения ваших объектов и нарушают статический анализ типа TS.

Было бы плохо, если бы это была дыра в системе типов? Я имею в виду, что этот код действительно необычен: let x: number[] = []; x[3] = 0; и если вы хотите делать именно такие вещи, то, возможно, вам стоит объявить свой массив let x: number?[] .

Он не идеален, но я думаю, что он достаточно хорош для большинства случаев использования в реальном мире. Если вы пурист, которому нужна система звуковых типов, вам непременно следует обратить внимание на другой язык, потому что система типов TS в любом случае не является звуковой. Что вы думаете?

Вот почему я сказал, что вам также нужна возможность преобразования в не разреженный массив
type, поэтому вы можете инициализировать массив самостоятельно без производительности
влияние.
Я бы согласился только на различие между (тем, что должно быть) разреженным
массивы и неразреженные массивы по типу.

Для тех, кто не знает, почему это важно, по той же причине, что и вы
хотелось бы разницы между T и T|null .

В 9:11, пт, 11 сентября 2015 г. jods [email protected] написал:

@Griffork https://github.com/Griffork
Я не знаю ... это сбивает с толку.

Таким образом, мы можем заставить разреженные массивы иметь тип T | undefined (что бы
перейти на тип T, используя for ... of и другие «безопасные» операции)

Из-за причуды в JS это не работает. Предположим let arr:
(T | не определено) [].
Так что я могу сделать следующее: arr [0] = undefined.
Если я это сделаю, то использование этих "безопасных" функций _will_ вернет undefined
для первого слота. Таким образом, в arr.forEach (x => ...) вы не можете сказать, что x: T.
По-прежнему должно быть x: T | undefined.

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

Это не очень похоже на TS по духу. Может я ошибаюсь но мне кажется
философия TS заключается в том, что типы являются лишь дополнительным слоем поверх JS.
и они не влияют на генерацию кода. Это имеет значение для производительности
частичная корректность типов, что мне не очень нравится.

TS, очевидно, не может защитить вас от всего в JS, и есть
несколько функций / конструкций, которые вы можете вызывать из действительного TS, которые имеют
динамическое воздействие на тип среды выполнения ваших объектов и нарушение статического TS
типовой анализ.

Было бы плохо, если бы это была дыра в системе типов? Я имею в виду этот код
действительно не является обычным явлением: let x: number [] = []; х [3] = 0; и если это
то, что вы хотите делать, тогда, возможно, вам следует объявить свой массив let
x: число? [].

Он не идеален, но я думаю, что он достаточно хорош для большинства случаев использования в реальном мире.
Если вы пурист, которому нужна звуковая система, то вам, безусловно, следует
посмотрите на другой язык, потому что система типов TS в любом случае не работает. Что
ты думаешь?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139408240
.

@ jods4 Под «исправлением» я имел в виду «исправление», ИМХО, недостатка дизайна языка JS. Не в TypeScript, а в самом _JavaScript_.

@jods Я жалуюсь на JS, а не на TS. Признаюсь, это не по теме.

В чт, 10 сентября 2015 г., 19:19 Griffork [email protected] написал:

Вот почему я сказал, что вам также нужна возможность преобразования в не разреженный массив
type, поэтому вы можете инициализировать массив самостоятельно без производительности
влияние.
Я бы согласился только на различие между (тем, что должно быть) разреженным
массивы и неразреженные массивы по типу.

Для тех, кто не знает, почему это важно, по той же причине, что и вы
хотелось бы разницы между T и T|null .

В 9:11, пт, 11 сентября 2015 г. jods [email protected] написал:

@Griffork https://github.com/Griffork
Я не знаю ... это сбивает с толку.

Таким образом, мы можем заставить разреженные массивы иметь тип T | undefined (что бы
перейти на тип T, используя for ... of и другие «безопасные» операции)

Из-за причуды в JS это не работает. Предположим let arr:
(T | не определено) [].
Так что я могу сделать следующее: arr [0] = undefined.
Если я это сделаю, то использование этих "безопасных" функций _will_ вернет undefined
для первого слота. Таким образом, в arr.forEach (x => ...) вы не можете сказать, что x: T.
По-прежнему должно быть x: T | undefined.

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

Это не очень похоже на TS по духу. Может я ошибаюсь но мне кажется
философия TS заключается в том, что типы являются лишь дополнительным слоем поверх
JS
и они не влияют на генерацию кода. Это имеет значение для производительности
частичная корректность типов, что мне не очень нравится.

TS, очевидно, не может защитить вас от всего в JS, и есть
несколько функций / конструкций, которые вы можете вызывать из действительного TS, которые
имеют
динамическое воздействие на тип среды выполнения ваших объектов и нарушение статического TS
типовой анализ.

Было бы плохо, если бы это была дыра в системе типов? Я имею в виду этот код
действительно не является обычным явлением: let x: number [] = []; х [3] = 0; и если это
то, что вы хотите сделать, тогда, возможно, вам следует объявить свой массив
позволять
x: число? [].

Он не идеален, но я думаю, что он достаточно хорош для большинства случаев использования в реальном мире.
Если вы пурист, которому нужна звуковая система, то вам следует
конечно
посмотрите на другой язык, потому что система типов TS в любом случае не работает.
Что
ты думаешь?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139408240>
.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139409349
.

А что касается моего утверждения с длинами массивов, мы могли бы действовать, исходя из предположения, что все обращения к массиву находятся в границах, а доступ за пределы не определен, если явно не указан в интерфейсе. Это очень похоже на то, что делает C / C ++, и это позволит как лучше печатать, так и, возможно, целую загрузку оптимизаций компилятора, если кто-то решит в конечном итоге написать сторонний компилятор, который использует спецификацию языка, но не так обеспокоен по поводу соответствие излучению.

Я знаю, что поддержка соответствия неопределенного поведения C / C ++ кажется на первый взгляд очень глупой, но я думаю, что в этом случае оно того стоит. Редко можно увидеть что-то, что на самом деле делается лучше, если доступ за пределы установлен. 99,99% случаев, которые я видел для этого, - это просто чрезвычайно резкий запах кода, почти всегда выполняемый людьми, которые почти не знакомы с JavaScript.

(По моему опыту, большинство из этих людей даже не слышали о CoffeeScript, тем более о TypeScript. Многие из них даже не знают о новой версии JS, которая была только что доработана и стандартизирована, ES2015.)

Есть ли какое-то решение по этому поводу?

За исключением наличия типа, не допускающего значения NULL, для TypeScript все еще кажется полезным сбой, если кто-то пытается получить доступ к свойству переменной, которая _assuredly_ null.

var o = null;
console.log(o.x);

... должен потерпеть неудачу.

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

Похоже, есть три варианта, предполагающих, что проверка границ не применяется к массивам во время компиляции:

  1. Считается, что индексирование массива (и любой доступ к произвольному элементу массива по индексу) возвращает тип, допускающий значение NULL, даже для массивов типов, не допускающих значения NULL. По сути, "метод" [] имеет сигнатуру типа T? . Если вы знаете, что выполняете только индексирование с проверкой границ, вы можете преобразовать T? в T! в коде вашего приложения.
  2. Индексирование массива возвращает точно такой же тип (с той же допускающей значение NULL), что и параметр универсального типа массива, и предполагается, что весь доступ к массиву проверяется приложением по границам. Доступ за границу вернет undefined и не будет обнаружен проверкой типов.
  3. Ядерный вариант: все массивы жестко запрограммированы в языке как допускающие значение NULL, и попытки использовать массивы, не допускающие значения NULL, не проходят проверку типов.

Все это применимо и к основанному на индексе доступу к свойствам объектов, например, object['property'] где object имеет тип { [ key: string ]: T! } .

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

Возникает второй вопрос, должны ли типы быть по умолчанию не допускающими значения NULL или по умолчанию допускающими значение NULL. Похоже, что в любом случае было бы полезно иметь синтаксис как для явно допускающих значение NULL, так и для явно не допускающих значения NULL типов для обработки универсальных типов; например, представьте себе метод get в классе контейнера (например, Map), который принимает какое-то значение и, возможно, возвращает тип, _ даже если контейнер содержит только значения, не допускающие значения NULL _:

class Container<K,V> {
  get(key: K): V? {
    // fetch from some internal data structure and return the value, if it exists
    // return null otherwise
  }
}

// only non-nullable values allowed in the container
const container = new Container<SomeKeyClass!, SomeValueClass!>();
const val: SomeValueClass!;
// ... later, we attempt to read from the container with a get() call
// even though only non-nullables are allowed in the container, the following should fail:
// get() explicitly returns null when the item can't be found
val = container.get(someKey);

Точно так же мы могли бы (это менее веский аргумент) захотеть, чтобы наш контейнерный класс принимал только ненулевые ключи при вставках, даже при использовании типа ключа, допускающего значение NULL:

class Container<K, V> {
  insert(key: K!, val: V): void {
    // put the val in the data structure
    // the key must not be null here, even if K is elsewhere a nullable type
  }
}

const container = new Container<SomeKeyClass?, SomeValueClass>();
container.insert(null, new SomeValueClass()); // fails

Таким образом, независимо от того, изменяется ли значение по умолчанию, кажется, что было бы полезно иметь явный синтаксис как для типов, допускающих значение NULL, так и для типов, не допускающих значения NULL. Если я чего-то не упускаю?

В точке, где есть синтаксис для обоих, по умолчанию кажется, что это может быть флаг компилятора, похожий на --noImplicitAny . Лично я бы проголосовал за то, чтобы значение по умолчанию оставалось неизменным до выпуска версии 2.0, но и то, и другое кажется нормальным, если есть аварийный люк (по крайней мере, временно).

Я бы предпочел второй вариант, поскольку даже если он приведет к неопределенному поведению доступа за пределы доступа (в области набора TS), я думаю, что это хороший компромисс для этого. Это может значительно увеличить скорость, и с этим проще бороться. Если вы действительно ожидаете, что доступ за границу возможен, вам следует либо явно использовать тип, допускающий значение NULL, либо привести результат к типу, допускающему значение NULL (что всегда возможно). И, как правило, если массив не допускает значения NULL, любой доступ за пределы почти всегда является ошибкой, которая в какой-то момент должна резко проявиться (это недостаток JS).

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

Вот сравнение с каждым вариантом на примере функции суммирования (примитивы являются наиболее проблематичными):

// Option 1
function sum(numbers: !number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += <!number> numbers[i]
  }
  return res
}

// Option 2
function sum(numbers: !number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += numbers[i]
  }
  return res
}

// Option 3
function sum(numbers: number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += <!number> numbers[i]
  }
  return res
}

Другой пример: функция map .

// Option 1
function map<T>(list: !T[], f: (value: !T, index: !number) => !T): !T[] {
  let res: !T[] = []
  for (let i = 0; i < list.length; i++) {
    res.push(f(<!T> list[i], i));
  }
  return res
}

// Option 2
function map<T>(list: !T[], f: (value: !T, index: !number) => !T): !T[] {
  let res: !T[] = []
  for (let i = 0; i < list.length; i++) {
    res.push(f(list[i], i));
  }
  return res
}

// Option 3
function map<T>(list: T[], f: (value: !T, index: !number) => !T): T[] {
  let res: T[] = []
  for (let i = 0; i < list.length; i++) {
    const entry = list[i]
    if (entry !== undefined) {
      res.push(f(<!T> entry, i));
    }
  }
  return res
}

Другой вопрос: какого типа entry в каждом из них? !string , ?string или string ?

declare const regularStrings: string[];
declare const nullableStrings: ?string[];
declare const nonnullableStrings: !string[];

for (const entry of regularStrings) { /* ... */  }
for (const entry of nullableStrings) { /* ... */  }
for (const entry of nonnullableStrings) { /* ... */  }

Третий вариант был немного насмешливым: stuck_out_tongue:

Re: ваш последний вопрос:

declare const regularStrings: string[];
declare const nullableStrings: string?[];
declare const nonNullableStrings: string![]; // fails typecheck in option three

for(const entry of regularStrings) {
  // option 1: entry is of type string?
  // option 2: depends on default nullability
}

for(const entry of nullableStrings) {
  // option 1 and 2: entry is of type string?
}

for(const entry of nonNullableStrings) {
  // option 1: entry is of type string?
  // option 2: entry is of type string!
}

В некоторых случаях - когда вы хотите вернуть тип, не допускающий значения NULL, и вы получаете его, например, из массива - вам придется выполнить дополнительное приведение с первым вариантом, если вы в другом месте гарантировали, что нет неопределенных значений в массиве (требование этой гарантии не меняется независимо от того, какой подход используется, нужно только ввести as string! ). Лично я по-прежнему предпочитаю его, потому что он и более явный (вы должны указать, когда вы принимаете потенциально опасное поведение, в отличие от того, что это происходит неявно), и более согласованный с тем, как работает большинство контейнерных классов: например, get карты Функция Map.prototype.get возвращает значение, допускающее значение NULL, тогда object['property'] вероятно, должно делать то же самое, поскольку они дают аналогичные гарантии обнуляемости и используются аналогичным образом. Это оставляет массивы как нечетные, где могут закрасться ошибки нулевых ссылок, и где произвольный доступ может быть не обнуляемым системой типов.

Определенно есть другие подходы; например, в настоящее время Flow использует вариант два , и последний раз я проверил, что SoundScript сделал разреженные массивы явно незаконными в их спецификации (ну, строгий режим / «SaneScript» делает их незаконными, а SoundScript - это надмножество новых правил), что в некоторой степени позволяет обойти проблему, хотя им все равно нужно будет выяснить, как справиться с ручным изменением длины и с начальным распределением. Я подозреваю, что они приблизятся к первому варианту в выборе компромисса между удобством и безопасностью - то есть будет менее удобно писать, но более безопасно - из-за их упора на надежность системы типов, но, вероятно, он будет выглядеть несколько иначе чем любой из этих подходов из-за запрета на разреженные массивы.

Я думаю, что аспект производительности является чрезвычайно теоретическим на данный момент, поскольку AFAIK TypeScript будет продолжать генерировать один и тот же JS независимо от приведений для любого выбора здесь, а базовые виртуальные машины будут продолжать проверять границы массивов под капотом независимо. Так что я не слишком увлечен этим аргументом. На мой взгляд, вопрос в основном связан с удобством и безопасностью; Мне кажется, что безопасность здесь стоит компромисса с удобством. Конечно, любой из них является улучшением по сравнению с тем, что все типы допускают значение NULL.

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


TypeScript действительно нужен способ утверждения, поскольку утверждения часто
используется для помощи в проверке статического типа на других языках. Я видел в
Код V8 базируется на макросе UNREACHABLE(); который позволяет предположениям быть
немного безопаснее, сбой программы при нарушении инварианта. C ++
имеет static_assert для статических утверждений, помогающих при проверке типов.

Во вторник, 20 октября 2015 г., в 4:01, Мэтт Бейкер [email protected]
написал:

Третий вариант был в некотором роде насмешливым предложением [изображение:
: stuck_out_tongue:]

Re: ваш последний вопрос:

объявить const regularStrings: string []; объявить const nullableStrings: string? []; объявить const nonNullableStrings: string! []; // не выполняется проверка типа в третьем варианте
for (постоянная запись регулярных строк) {
// вариант 1: запись имеет строковый тип?
// вариант 2: зависит от допустимости значений по умолчанию
}
for (константная запись nullableStrings) {
// вариант 1 и 2: запись имеет строковый тип?
}
for (константная запись nonNullableStrings) {
// вариант 1: запись имеет строковый тип?
// вариант 2: запись имеет строковый тип!
}

В некоторых случаях - когда вы хотите вернуть тип, не допускающий значения NULL, и вы
получить его из массива, например - вам нужно будет сделать дополнительное приведение
с первым вариантом, если вы в другом месте гарантировали, что нет undefined
значения в массиве (требование этой гарантии не меняется
независимо от того, какой подход используется, нужно вводить только строку!).
Лично я по-прежнему предпочитаю его, потому что и то, и другое более явное (вы должны
указать, когда вы ведете потенциально опасное поведение, а не
происходит неявно) и более соответствует тому, как большинство контейнерных классов
работа: например, функция get карты явно возвращает типы, допускающие значение NULL.
(он возвращает объект, если он существует под ключом, или null, если его нет),
и если Map.prototype.get возвращает значение, допускающее значение NULL, тогда object ['property']
вероятно, должны сделать то же самое, поскольку они дают аналогичные гарантии относительно
обнуляемость и используются аналогичным образом. Что оставляет массивы как лишние
где ошибки нулевой ссылки могут закрасться, и где произвольный доступ
разрешено системой типов не допускать значения NULL.

Определенно есть другие подходы; например, в настоящее время Flow использует
вариант два http://flowtype.org/docs/nullable-types.html , и последний я
проверил, что SoundScript сделал разреженные массивы явно незаконными в их спецификации
https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf
(ну, строгий режим / "SaneScript" делает их незаконными, а SoundScript - это
надмножество новых правил), что в некоторой степени обходит проблему
хотя им все еще нужно будет выяснить, как справиться с ручной длиной
изменения и с первоначальным размещением.

Я думаю, что на данный момент аспект производительности носит исключительно теоретический характер,
поскольку AFAIK TypeScript продолжит генерировать один и тот же JS независимо от
бросает здесь любой выбор, и базовые виртуальные машины будут продолжать
проверяющие границы массивы под капотом в любом случае. Так что я не слишком увлечен
этот аргумент. На мой взгляд, вопрос в основном связан с удобством vs.
безопасность; Мне кажется, что безопасность здесь стоит компромисса с удобством. Из
Конечно, любой из них является улучшением по сравнению с тем, что все типы допускают значение NULL.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -149468527
.

Исайя Медоуз

Может, нам просто начать называть их non-void типами? В любом случае, я считаю ошибкой явное определение non-void с помощью T! или !T . Его трудно читать по-человечески, а также трудно справиться со всеми случаями для компилятора TypeScript.

Основная проблема, которую я вижу с типами non-void по умолчанию, заключается в том, что это критическое изменение. А что, если мы просто добавим еще какой-то статический анализ, похожий на Flow, который вообще не изменит поведения, но позволит отловить больше ошибок? Тогда мы сможем отловить большинство ошибок этого класса сейчас, но не менять синтаксис, и в будущем будет намного проще ввести флаг компилятора или поведение по умолчанию, которое не является критическим изменением.

`` .ts
// функция успешно компилируется
функция len (x: строка): number {
вернуть x.length;
}

len ("работает"); // 5
len (ноль); // ошибка, длина свойства не равна нулю

``` .ts
function len(x: string): number {
    if (x === null) {
        return -1;
    }
    return x.length;
}

len("works"); // 5
len(null); // null

На самом деле здесь будет происходить моделирование входных данных как non-void , но добавление void неявно при обработке в функции. Точно так же тип возврата - non-void если только он не может явно вернуть null или undefined

Мы также можем добавить тип ?T или T? , который принудительно проверяет значение null (и / или undefined) перед использованием. Лично мне нравится T? , но есть прецедент использования ?T с Flow.

`` .ts
функция len (x:? строка): number {
вернуть x.length; // ошибка: нет свойства длины для типа? строка, необходимо использовать защиту типа
}

One more example -- what about using function results?

``` .js
function len(x: string): number {
    return x.length;
}

function identity(f: string): string {
    return f;
}

function unknown(): string {
    if (Math.random() > 0.5) {
        return null;
    }
    return "maybe";
}

len("works"); // 5
len(null); // error, no property length of null

identity("works"); // "works": string
identity(null); // null: void
unknown(); // ?string

len(identity("works")); // 5
len(identity(null)); // error, no property length of null
len(unknown()); // error: no length property on type ?string, you must use a type guard

Под капотом на самом деле здесь происходит то, что TypeScript делает вывод о том, может ли тип иметь значение NULL, проверяя, обрабатывает ли он значение NULL, и ему присваивается возможное значение NULL.

Единственная сложная часть здесь - это то, как взаимодействовать с файлами определений. Я думаю, что это можно решить, если по умолчанию предполагается, что файл определения, объявляющий function(t: T) выполняет проверку null / void , как и во втором примере. Это означает, что эти функции смогут принимать нулевые значения без генерации ошибки компилятором.

Теперь это позволяет две вещи:

  1. Постепенное внедрение синтаксиса типа ?T , необязательные параметры которого уже будут заменены.
  2. В будущем может быть добавлен флаг компилятора --noImplicitVoid , который будет обрабатывать файлы объявлений так же, как файлы скомпилированного кода. Это было бы "нарушением", но если это будет сделано в будущем, большинство библиотек примут на вооружение лучшую практику использования ?T когда типом может быть void и T когда не может. Это также будет подпаданием, поэтому затронуты только те, кто решит использовать его. Это также может потребовать использования синтаксиса ?T в случае, когда объект может быть недействительным.

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

Префикс ? вариант также используется в аннотациях компилятора Closure, IIRC.

Во вторник, 17 ноября 2015 г., 13:37 Том Жак [email protected] написал:

Должны ли мы просто начать называть их непустыми типами? В любом случае думаю
явно определяя непустоту с помощью T! или! T - ошибка. Это трудно
читать по-человечески, а также трудно справиться со всеми случаями для
компилятор TypeScript.

Основная проблема, которую я вижу по умолчанию с непустыми типами, заключается в том, что это
ломка сдачи. Что, если мы просто добавим еще статический анализ,
похож на Flow, который вообще не меняет поведения, но ловит
больше ошибок? Тогда мы сможем отловить большинство ошибок этого класса сейчас, но
не меняйте синтаксис, и в будущем будет намного проще
ввести флаг компилятора или поведение по умолчанию, которое не нарушает
изменять.

// функция успешно компилируется function len (x: string): number {
вернуть x.length;
}

len ("работает"); // 5
len (ноль); // ошибка, длина свойства не равна нулю

функция len (x: строка): number {
if (x === null) {
возврат -1;
}
вернуть x.length;
}

len ("работает"); // 5
len (ноль); // нулевой

Что на самом деле здесь происходит, так это моделирование входных данных как непустых,
но добавляя void неявно, когда он обрабатывается в функции. Так же,
тип возвращаемого значения не является недействительным, если он не может явно возвращать null или
неопределенный

Мы также можем добавить? T или T? type, который заставляет null (и / или
undefined) проверьте перед использованием. Лично мне нравится T ?, но есть прецедент
использовать? T с Flow.

функция len (x:? строка): number {
вернуть x.length; // ошибка: нет свойства длины для типа? строка, необходимо использовать защиту типа
}

Еще один пример - как насчет использования результатов функции?

функция len (x: строка): number {
вернуть x.length;
}
идентификатор функции (f: строка): строка {
return f;
}
function unknown (): string {
if (Math.random ()> 0,5) {
return null;
}
вернуть "возможно";
}

len ("работает"); // 5
len (ноль); // ошибка, длина свойства не равна нулю

личность («работает»); // "работает": строка
личность (ноль); // null: void
неизвестный(); // ?нить

len (личность ("работает")); // 5
len (личность (ноль)); // ошибка, длина свойства не равна нулю
len (неизвестно ()); // ошибка: нет свойства длины для типа? строка, необходимо использовать защиту типа

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

Единственная сложная часть здесь - это то, как взаимодействовать с файлами определений. я
думаю, что это можно решить, если по умолчанию предполагается, что
файл определения, объявляющий функцию (t: T), выполняет нулевую / недействительную проверку, много
как второй пример. Это означает, что эти функции смогут выполнять
нулевые значения без компилятора, генерирующего ошибку.

Теперь это позволяет две вещи:

  1. Постепенное внедрение синтаксиса типа? T, из которого необязательно
    параметры уже будут заменены.
  2. В будущем может быть добавлен флаг компилятора --noImplicitVoid.
    который будет обрабатывать файлы объявлений так же, как файлы скомпилированного кода. Этот
    будет "ломаться", но если сделать это далеко в будущем, большинство
    библиотеки примут лучшие практики использования? T, когда тип может
    быть недействительным и T, когда он не может. Это также будет подпаданием, поэтому только те
    кто выберет его использовать, будет затронут.

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

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -157463734
.

Хорошая точка зрения. Также есть много общего с существующими объявлениями необязательных параметров:

`` .ts
interface withOptionalProperty {
o ?: строка
}
interface withVoidableProperty {
о:? строка
}

функция withOptionalParam (o ?: строка) {}
функция withVoidableParam (o:? строка) {}
`` ''

На самом деле они используют префикс . Они используют ? для явных типов, допускающих значение NULL, и ! для типов, не допускающих значения NULL, при этом по умолчанию используется значение NULL.

Различие между аннулируемым и обнуляемым имеет большой смысл. : +1:

Я чувствую, что это ходит по кругу.

Вы все читали выше, почему определение, которое может быть оспорено / не может быть оспорено, не решит основную проблему?

@Griffork Я прочитал все комментарии. Я признаю, что то, что я сказал, является своего рода перефразированием / комбинацией того, что сказали другие, но я думаю, что это наиболее реалистичный путь вперед. Я не знаю, что вы видите в качестве основной проблемы, но для меня основная проблема заключается в том, что null и undefined являются частью каждого типа, и компилятор в настоящее время не обеспечивает безопасность при попытке использовать аргумент типа T . Согласно моему примеру:

`` .ts
функция len (x: строка): number {
вернуть x.length;
}

len ("работает");
// Ошибки нет - 5

len (ноль);
// Компилятор допускает это, но здесь должна произойти ошибка с чем-то вроде
// ошибка: нет свойства 'length' со значением NULL

len (не определено);
// Компилятор допускает это, но здесь должна произойти ошибка с чем-то вроде
// ошибка: нет свойства 'length' неопределенного значения
`` ''

Это мой взгляд на фундаментальную проблему _language_ - отсутствие безопасности. В значительной степени это можно исправить, просмотрев поток аргументов в функциях с помощью статического анализа и анализа типов и выдав ошибку, когда что-то может быть сделано небезопасно. Когда код доступен, вообще не обязательно должен быть какой-либо тип ?T потому что этот анализ может быть выполнен (хотя и не во всех случаях всегда идеально точно). Причина добавления типа ?T заключается в том, что он вызывает принудительную проверку безопасности и делает намерение программиста очень ясным.

Проблема с реализацией - обратная совместимость. Если бы команда TS завтра выпустила изменение, согласно которому теперь тип T является недействительным по умолчанию, это нарушило бы существующий код, который в настоящее время принимает и обрабатывает пустые входные данные с этими сигнатурами типа. Члены команды TS заявили в этом же выпуске, что не желают вносить такие серьезные изменения. Они готовы что-то сломать, если эффект достаточно мал, а польза достаточно велика, но это будет иметь слишком большое влияние.

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

Первая часть:

  • Добавьте анализ, чтобы определить, когда типы null / undefined / void могут быть переданы функциям, которые их не обрабатывают (работает только при наличии кода TS, а не в файлах определений).
  • Добавьте ?T type, который принудительно проверяет недействительность перед использованием аргумента. На самом деле это просто синтаксический сахар вокруг типа параметра на уровне языка.
  • Эти две функции могут быть реализованы независимо, поскольку каждая имеет свои индивидуальные достоинства.

Часть вторая:

  • Ждать. Позже, после того, как будет представлена ​​первая часть, и у сообщества и пользователей TS было время использовать эти функции, стандартом будет использование T если тип не равен null, и ?T если тип может быть нулевым. Это не гарантируется, но я думаю, что это будет очевидная лучшая практика.
  • В качестве отдельной функции добавьте параметр компилятора --noImplicitVoid который требует, чтобы типы были ?T если они могут быть недействительными. Это просто компилятор, реализующий уже существующие передовые практики. Если есть файл определения, который не соответствует передовой практике, он был бы неверным, но именно поэтому он включен.
  • Если вы действительно хотите быть строгим, флаг может принимать аргументы, указывающие, к каким каталогам / файлам он должен применяться. Затем вы можете применить изменение только к своему коду и исключить node_modules .

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

@tejacques Я, например, полностью

@tejacques - FWIW, я полностью согласен с вашей оценкой и предложением. Будем надеяться, что команда TS согласится :)

Собственно, две вещи:

Во-первых, я не уверен, что упомянутый в первой части анализ Flow-подобный необходим. Хотя это очень круто и полезно, я, конечно, не хотел бы, чтобы он содержал типы voidable / ?T , которые кажутся гораздо более осуществимыми в текущем дизайне языка и обеспечивают гораздо более долгосрочную ценность.

Я бы также сформулировал --noImplicitVoid немного по-другому - скажем, он запрещает присваивать null и undefined неотменяемым (то есть стандартным) типам, а не "требовать, чтобы типы были ?T если они могут быть признаны недействительными ". Я почти уверен, что мы имеем в виду одно и то же, только семантику; фокусируется на использовании, а не на определении, что, если я понимаю, как работает TS, - это единственное, что он действительно может обеспечить.

И вот что пришло в голову только что: у нас было бы тогда четыре уровня оспариваемости (это также верно и для Flow, что, я думаю, во многом вдохновляет в этом разговоре):

interface Foo {
  w: string;
  x?: string;
  y: ?string;
  z?: ?string;
}

В --noImplicitVoid w может быть только допустимой строкой. Это огромная победа для безопасности типов. Прощай, ошибка на миллиард долларов! Без --noImplicitVoid , конечно, единственным ограничением является то, что он должен быть указан, но он может быть нулевым или неопределенным. Я считаю, что это довольно опасное поведение языка, потому что похоже, что он гарантирует больше, чем есть на самом деле.

x полностью снисходителен при текущих настройках. Это может быть строка, значение NULL, undefined, и оно даже не обязательно должно присутствовать в объектах, которые его реализуют. В --noImplicitVoid все становится немного сложнее ... вы можете не определять его, но если вы _do_ что-то зададите, разве это не будет недействительным? Я думаю, что Flow обрабатывает это так, что вы можете установить x равным undefined (имитируя несуществование), но не null . Однако это может быть слишком самоуверенным для TypeScript.

Кроме того, было бы логично требовать проверки на недействительность x перед его использованием, но это снова будет критическим изменением. Может ли такое поведение быть частью флага --noImplicitVoid ?

y может быть установлен в любое строковое или пустое значение, но _должен_ присутствовать в какой-либо форме, даже если он пуст. И, конечно же, для доступа к нему требуется проверка на аннулирование. Такое поведение может показаться немного неожиданным. Должны ли мы рассматривать _not_ установку как то же самое, что установка на undefined ? Если да, то чем он будет отличаться от x , кроме требования проверки на недействительность?

И, наконец, z указывать не нужно, может быть установлено абсолютно что угодно (ну, за исключением, конечно, не строки), и (по уважительной причине) перед доступом к нему требуется проверка недействительности. Здесь все имеет смысл!

Есть небольшое совпадение между x и y , и я подозреваю, что x в конечном итоге станет устаревшим со стороны сообщества, предпочитая форму z для максимальной безопасности. .

@tejacques

Когда код доступен, вообще не обязательно должен быть какой-либо тип? T, потому что этот анализ может быть выполнен (хотя и не в каждом случае всегда с идеальной точностью).

Это утверждение неверно. Многие аспекты допустимости значений NULL не могут быть учтены даже при наличии полного исходного кода.

Например: при вызове интерфейса (статически) вы не можете знать, правильно ли передать значение null, если интерфейс не аннотирован соответствующим образом. В системе структурных типов, такой как TS, даже нелегко узнать, какие объекты являются реализациями интерфейса, а какие нет. В общем, вам все равно, но если вы хотите вывести допустимость значений NULL из исходного кода, вы бы это сделали.

Другой пример: если у меня есть массив list и функция unsafe(x) , исходный код которой показывает, что он не принимает аргумент null , компилятор не может сказать, если это безопасна строка или нет: list.filter(unsafe) . И на самом деле, если вы не можете статически знать, каким будет все возможное содержимое list , это невозможно сделать.

Другие случаи связаны с наследованием и не только.

Я не говорю, что инструмент анализа кода, который бы сигнализировал о вопиющем нарушении нулевых контрактов, не имеет ценности (он имеет). Я просто указываю, что преуменьшение полезности нулевых аннотаций, когда доступен исходный код, является ИМХО ошибкой.

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

@tejacques, мой плохой, я неправильно прочитал ваш комментарий (по какой-то причине мой мозг решил void===null :( Я виню только то, что просыпаюсь).
Спасибо за дополнительный пост, он имел для меня гораздо больше смысла, чем ваш исходный пост. Мне вообще-то нравится эта идея.

Я отвечу вам троим по отдельности, потому что писать это в одном сообщении - это нечитаемый объект.

@Griffork Нет проблем. Иногда бывает сложно передать все правильно с помощью текста, и я думаю, что все равно стоило уточнить.

@dallonf Твой перефраз - это именно то, что я имею в виду - мы на одной странице.

Я думаю, что разница между x?: T и y: ?T будет заключаться в использовании всплывающих подсказок / функций, а также в использовании набора текста и защиты.

Объявление в качестве необязательного аргумента изменяет использование всплывающих подсказок / функций, поэтому становится ясно, что это необязательно:
a?: T не нужно передавать в качестве аргумента при вызове функции, его можно оставить пустым
Если в объявлении нет ?: оно должно быть передано в качестве аргумента при вызове функции и не может быть оставлено пустым.

w: T - обязательный аргумент, отличный от void--noImplicitVoid )
не требует охраны

x?: T - необязательный аргумент, поэтому тип действительно T | undefined
требуется if (typeof x !== 'undefined') охранник.
Обратите внимание на тройной глиф !== для точной проверки undefined .

y: ?T - обязательный аргумент, а тип действительно T | void
требуется if (y == null) охранник.
Обратите внимание на двойной глиф == который соответствует как null и undefined ie void

z?: ?T - необязательный аргумент, а тип действительно T | undefined | void который равен T | void
требуется if (z == null) охранник.
Еще раз обратите внимание на двойной глиф == который соответствует как null и undefined ie void

Как и в случае со всеми необязательными аргументами, у вас не может быть обязательных аргументов, следующих за необязательными. Так вот в чем будет разница. Теперь вы также можете просто установить null guard для необязательного аргумента, и это тоже сработает, но ключевое отличие состоит в том, что вы не можете передать значение null функции, если вы назвал это; однако вы можете передать undefined .

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

Изменить: обновлены формулировки и исправлены некоторые опечатки.

@ jods4 Я согласен практически со всем, что вы сказали. Я не пытаюсь преуменьшить важность не void набора текста. Я просто пытаюсь продвигать его по одной фазе за раз. Если команда TS не сможет сделать это позже, по крайней мере, нам будет лучше, и если они смогут сделать это в будущем после выполнения дополнительных проверок и ?T то эта миссия выполнена.

Я думаю, что случай с Arrays действительно непростой. Вы всегда можете сделать что-то вроде этого:

`` .ts
function numToString (x: number) {
вернуть x.toString ();
}
var nums: number [] = массив (100);
numToString (числа [0]); // Вы облажались!

You can try to do something specifically for uninitialized arrays, like typing the `Array` function as `Array<?T>` / `?T[]` and upgrading it to `T[]` after a for-loop initializing it, but I agree that you can't catch everything. That said, that's already a problem anyway, and arrays typically don't even send uninitialized values to `map`/`filter`/`forEach`.

Here's an example -- the output is the same on Node/Chrome/IE/FF/Safari.

``` .ts
function timesTwo(x: number) {
    return x * 2;
}
function all(x) {
    return true;
}
var nums: number[] = Array(100);
nums.map(timesTwo);
// [undefined x 100]
nums.filter(all);
// []
nums.forEach(function(x) { console.log(x); })
// No output

Это действительно не так сильно помогает, поскольку это неожиданно, но сегодня это не ошибка реального JavaScript.

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

Вот пример. Предположим, что --noImplicitVoid выключен

`` .ts
интерфейс ITransform{
(x: T): U;
}

interface IHaveName {
имя: строка;
}

преобразование функции(x: T, fn: ITransform) {
вернуть fn (x);
}

var named = {
имя: "Фу"
};

var errorName = {
название: 1234
};

var namedNull = {
имя: null
};

var someFun = (x: IHaveName) => x.name;
var someFunHandlesVoid = (x: IHaveName) => {
if (x! = null && x.name! = null) {
вернуть x.name;
}
вернуть «Без имени»;
};

All of the above code compiles just fine -- no issues. Now let's try using it

``` .ts
someFun(named);
// "Foo"
someFun(wrongName);
// error TS2345: Argument of type '{ name: number; }' is not assignable to parameter
// of type 'IHaveName'.
//   Types of property 'name' are incompatible.
//     Type 'number' is not assignable to type 'string'.
someFun(null);
// Not currently an error, but would be something like this:
// error TS#: Argument of type 'null' is not assignale to parameter of type 'IHaveName'.
someFun(namedNull);
// Not currently an error, but would be something like this:
// error TS#: Argument of type '{ name: null; }' is not assignable to parameter of
// type 'IHaveName'.
//   Types of property 'name' are incompatible.
//     Type 'null' is not assignable to type 'string'.

someFunHandlesVoid(named);
// "Foo"
someFunHandlesVoid(wrongName);
// error TS2345: Argument of type '{ name: number; }' is not assignable to parameter
// of type 'IHaveName'.
someFunHandlesVoid(null);
// "No Name"
someFunHandlesVoid(namedNull);
// "No Name"

transform(named, someFun);
// "Foo"
transform(wrongName, someFun);
// error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: number; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
//     Types of property 'name' are incompatible.
//       Type 'string' is not assignable to type 'number'.
transform(null, someFun);
// Not currently an error, but would be something like this:
// error TS#: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate 'null' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
transform(namedNull, someFun);
// Not currently an error, but would be something like this:
// error TS#: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: null; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
//     Types of property 'name' are incompatible.
//       Type 'string' is not assignable to type 'null'.

transform(named, someFunHandlesVoid);
// "Foo"
transform(wrongName, someFunHandlesVoid);
// error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: number; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
transform(null, someFunHandlesVoid);
// "No Name"
transform(namedNull, someFunHandlesVoid);
// "No Name"

Вы правы, что всего не поймать, но многое можно поймать.

Последнее замечание - каким должно быть поведение вышеупомянутого, когда --noImplicitVoid включен?

Теперь someFun и someFunHandlesVoid проверяются по типу одинаково и выдают те же сообщения об ошибках, что и someFun . Несмотря на то, что someFunHandlesVoid действительно обрабатывает void, вызов его с помощью null или undefined является ошибкой, поскольку в подписи указано, что он принимает не void . Его нужно набрать как (x: ?IHaveName) : string чтобы принять null или undefined . Если мы изменим его тип, он продолжит работать, как и раньше.

Это та часть, которая является критическим изменением, но все, что нам нужно сделать, чтобы исправить это, - это добавить один символ ? к сигнатуре типа. У нас даже может быть другой флаг --warnImplicitVoid который делает то же самое, что и предупреждение, чтобы мы могли медленно мигрировать.

Я чувствую себя полным придурком из-за этого, но я собираюсь сделать еще один пост.

На данный момент я не уверен, что делать, чтобы продолжить. Есть идея получше? Можем ли мы:

  • продолжайте обсуждать / выяснять, как это должно себя вести?
  • превратить это в три предложения новых функций?

    • Расширенный анализ

    • Может быть / Тип варианта ?T

    • --noImplicitVoid параметр компилятора

  • ping членов команды TypeScript для ввода?

Я склоняюсь к новым предложениям и продолжаю их обсуждение, так как почти бесчеловечно просить команду TypeScript обсудить эту тему, учитывая, сколько она длится.

@tejacques

  • Вам не хватает typeof в примере тройного равенства с dallonf.
  • Кажется, вам не хватает ? в примере с jods4.

Как бы я ни думал, что материал должен оставаться в этой ветке, я думаю, что эта ветка на самом деле больше не «наблюдает» (может быть, больше похоже на то, что время от времени просматривается). Так что создание новых потоков определенно повысит популярность.
Но подождите несколько дней / неделю, чтобы люди сначала подняли голову и предоставили обратную связь. Вам нужно, чтобы ваше предложение было достаточно убедительным.

Изменить: запомненная уценка существует.

Комментировать эту ветку на данном этапе бесполезно. Даже если будет сделано предложение, которое команда TypeScript сочтет приемлемым (я пытался это сделать в августе), они никак не найдут его среди шума.

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

+1 за разделение, но пока опция --noImplicitVoid может
подождите, пока не будет реализован тип, допускающий значение NULL.

До сих пор мы в основном пришли к соглашению по синтаксису и семантике
типы, допускающие значение NULL, поэтому, если кто-то может написать предложение и реализацию
это было бы золотым. У меня есть предложение от аналогичного процесса
относительно перечислений других типов, но у меня просто не было времени
реализовать его за счет других проектов.

В среду, 18 ноября 2015 г., 21:24 Том Жак [email protected] написал:

Я чувствую себя полным придурком из-за этого, но я собираюсь сделать еще один
Почта.

На данный момент я не уверен, что делать, чтобы продолжить. Есть идея получше?
Можем ли мы:

  • продолжайте обсуждать / выяснять, как это должно себя вести?
  • превратить это в три предложения новых функций?

    • Расширенный анализ

    • Может быть / Тип варианта? T

    • --noImplicitVoid параметр компилятора

  • ping членов команды TypeScript для ввода?

Я склоняюсь к новым предложениям и продолжаю их обсуждение с тех пор, как
почти бесчеловечно просить команду TypeScript обсудить эту тему
учитывая, как долго это длится.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -157928828
.

+1 для опции --noImplicitNull (запретить присвоение void и null).

Я попытался смягчить эту проблему с помощью специального типа Op<A> = A | NullType . Кажется, работает неплохо. Смотрите здесь .

+1 для _-- noImplicitNull_, ПОЖАЛУЙСТА: +1:

+1 для --noImplicitNull

Следует ли это закрыть?

@Gaelan Given # 7140 объединен, если вы хотите подать новую, выделенную проблему для --noImplicitNull как предложено несколькими людьми здесь, то, вероятно, сделать это сейчас безопасно.

@isiahmeadows Тогда, наверное, лучше оставить это открытым.

Следует ли это закрыть?

Мы думаем, что https://github.com/Microsoft/TypeScript/issues/2388 является частью этой работы,

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

Я не уверен, что понимаю, какая семантика требуется от этого нового флага. Я бы порекомендовал открыть новый выпуск с четким предложением.

@mhegazy Идея, --noImplicitNull заключалась в том, что все должно быть явно ?Type или !Type . IMHO Я не думаю, что стоит использовать шаблон, когда есть другой флаг, который по умолчанию предполагает, что IIRC уже был реализован, когда были сами типы, допускающие значение NULL.

Закрытие, когда # 7140 и # 8010 объединены.

Извините, если я прокомментирую закрытый вопрос, но я не знаю, где лучше спросить, и я не думаю, что стоит выпускать новый выпуск, если нет интереса.
Можно ли обрабатывать неявное значение null для каждого файла?
Например, обрабатывать кучу файлов td с помощью noImplicitNull (потому что они исходят из определенно типизированного и задумывались таким образом), но обрабатывать мой источник как implicitNull?
Кто-нибудь найдет это полезным?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

MartynasZilinskas picture MartynasZilinskas  ·  3Комментарии

jbondc picture jbondc  ·  3Комментарии

dlaberge picture dlaberge  ·  3Комментарии

wmaurer picture wmaurer  ·  3Комментарии

remojansen picture remojansen  ·  3Комментарии