Обновление (3/2021): благодаря вашим отзывам команда Dart решила использовать синтаксис a? [B].

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

Например, чтобы объявить, что целое число может быть нулевым, вам нужно добавить ? после типа:

int? someInt; // someInt can be null.

Этот синтаксис вопросительного знака может быть вам знаком, если вы видели код Kotlin, Swift или C #. Но некоторые вещи отличаются - в частности, оператор нижнего индекса ([]), который вы чаще всего используете для доступа к списку или массиву. C # и Swift используют ?[]. Текущий план для Dart (и, кстати, для ECMAScript) состоит в том, чтобы вместо этого использовать ?.[]:

e1?.[e2] // null if e1 is null; otherwise it’s e1[e2]

Эта статья проведет вас за кулисами, чтобы объяснить причины этого решения и побудить вас взвесить свои мысли и предложения. Большая часть этого контента основана на комментарии Боба Нистрома (великодушный) по языковому вопросу # 376, который суммирует обсуждение между Бобом и владельцем спецификации NNBD, Лифом Петерсеном (листпетерсен).

Зачем нужна точка?

Оба варианта [] имеют преимущества.

e1?[e2]:

  • Следует за C # и Swift
  • Кратко
  • Аналогичен синтаксису оператора ! (который вы можете добавить после выражения, чтобы указать, что его значение не равно NULL, даже если оно имеет тип, допускающий значение NULL): e1![e2]

e1?.[e2]:

  • Похож на синтаксис каскада:
    e1..[e2] // cascade syntax
    e1?..[e2] // null-aware cascade syntax
  • Аналогичен синтаксису другого метода с нулевым значением: e1?.e2()
  • Естественно распространяется на других операторов
  • Избегает двусмысленности в следующем коде: { e1 ? [e2] : e3 }

Мы потратили некоторое время, пытаясь придумать способы избежать двусмысленности ?[. Проблема в том, что вы не можете определить, является ли код типа { e1 ? [e2] : e3 } литералом набора, содержащим результат условного выражения, или литералом карты, содержащим результат нижнего индекса с нулевым значением.

Если мы добавим круглые скобки, чтобы сделать этот код недвусмысленным, мы могли бы добавить их вокруг всего выражения - { (e1 ? [e2] : e3) } - делая его однозначно заданным литералом. Или мы могли бы добавить их вокруг первой части - { (e1 ? [e2]) : e3 }, что однозначно сделало бы ее литералом карты. Но из-за отсутствия круглых скобок парсер не знает, что делать.

Есть разные решения этой двусмысленности, но ни одно из них не кажется удовлетворительным. Один из подходов - полагаться на пробелы, чтобы различать варианты. В этом подходе вы всегда рассматриваете e1 ? [e2] как начало условного выражения, потому что между ? и [ есть пробел. И вы всегда относитесь к e1?[e2] как к нулевому нижнему индексу, потому что между этими двумя токенами нет пробела. Но использование пробелов может действительно навредить пользовательскому опыту.

Теоретически использование пробелов не проблема для форматированного кода. Но многие пользователи пишут неформатированный код Dart в качестве входных данных для форматтера. И этот формат ввода, таким образом, стал бы более чувствительным к пробелам и нестабильным в этом уголке языка. Пока что такие углы очень редки в Dart, и это хорошая особенность. (Один из таких углов состоит в том, что — — a и --a действительны, но означают разные вещи.)

Игнорируя двусмысленность, есть еще одна причина для использования точки: если мы добавим формы с нулевым значением для других операторов - e1?.+(e2) и т. Д. - мы, вероятно, захотим потребовать точку, и в этом случае требование ее для подстрочного индекса согласуется с этим будущим .

Еще одно дополнение, которое мы обсуждали для NNBD, - это синтаксис вызовов с нулевым значением. Если нам здесь не нужна точка, возникает та же проблема двусмысленности:

var wat = { e1?(e2):e3 }; // Map or set?

Какое бы исправление мы ни предложили для ?[, мы также должны будем применить к ?(.

Наконец, рассмотрим этот пример цепочки нижнего индекса:

someJson?[recipes]?[2]?[ingredients]?[pepper]

На наш взгляд, это выглядит не очень хорошо. Он сканирует не столько как цепочку методов, сколько как некоторую комбинацию инфиксных операторов - что-то вроде ??. Сравните это с этим кодом:

someJson?.[recipes]?.[2]?.[ingredients]?.[pepper]

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

Собирая все это вместе, кажется, что мы должны использовать форму ?.[ по следующим причинам:

  • Это позволяет избежать проблем с двусмысленностью. (Лексер уже рассматривает ?. как один токен с нулевым значением.)
  • Он естественным образом распространяется на вызов с нулевым значением.
  • Он распространяется на другие операторы с нулевым значением.
  • Это оставляет Dart более надежным языком ввода для средства форматирования.
  • В цепочке методов это выглядит вполне нормально.
  • Это делает визуально более понятным, какая часть цепочки методов может быть замкнута накоротко.

Что вы думаете?

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

Здесь вы можете найти дополнительную информацию:

ННБД:

Другие изменения и особенности языка Dart: