Irbisa · Шпаргалка

Паттерны и записи в Dart 3

Записи, деструктуризация, switch-выражения, if-case, sealed-типы, исчерпывающий разбор

Middle-разработчик · 19 вопросов

  1. 01

    Что такое запись (record) в Dart 3 и чем отличаются позиционные и именованные поля?

    Запись представляет собой анонимную неизменяемую группу значений фиксированного размера, например (200, 'ok') или (name: 'Ada', age: 36).

    // The type is the shape: nothing declares (int, String) anywhere.(int, String) parse(String raw) => (raw.length, raw.toUpperCase()); typedef User = ({String name, int age}); // a typedef only names a shape void main() {…
  2. 02

    Как деструктурировать запись в переменные и где ещё можно использовать деструктурирующий паттерн?

    Слева пишется паттерн в форме записи: final (code, body) = fetch(); для позиционных полей, final (:name, :age) = user; для именованных.

    typedef User = ({String name, int age}); (int, String) fetch() => (200, 'ok'); void main() {  final (code, body) = fetch(); // positional: bound by position…
  3. 03

    Чем отличаются switch-оператор и switch-выражение в Dart 3?

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

    enum Status { active, blocked, deleted } // Expression: produces a value; arms are `pattern => value`.String label(Status s) => switch (s) {      Status.active => 'Active',      Status.blocked || Status.deleted => 'Unavailable',…
  4. 04

    Что выведет этот код и почему последний поиск промахивается, хотя ключ выглядит таким же?

    void main() {  print((1, 'a') == (1, 'a'));  print((x: 1, y: 2) == (y: 2, x: 1));  print((1, 2) == (x: 1, y: 2));   final cache = <(int, String), String>{(1, 'en'): 'Hello'};  print(cache[(1, 'en')]);   final byTags = <(List<String>, int), String>{(['a'], 1): 'x'};  print(byTags[(['a'], 1)]);}

    Код выведет true, true, false, Hello, null.

  5. 05

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

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

    // wrong: every caller re-guesses the keys and castsMap<String, dynamic> loadOld() => {'items': <String>[], 'total': 0}; // right: the shape is part of the signature(List<String> items, int total) loadPage() => (['a', 'b'], 42);…
  6. 06

    Что такое сопоставление с образцом (pattern matching) в Dart 3 и какие старые идиомы оно заменяет?

    Паттерн описывает форму значения; сопоставление проверяет значение на соответствие и за один шаг связывает названные части с новыми переменными.

    // before: is-checks, casts and nested conditionsString describeOld(Object json) {  if (json is Map<String, dynamic>) {    final user = json['user'];    if (user is Map<String, dynamic> && user['name'] is String) {      return 'user ${user['name'] as String}';…
  7. 07

    Что такое sealed-класс, как он делает switch исчерпывающим и чем он лучше абстрактного класса для моделирования состояния?

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

    sealed class AuthState {} final class SignedOut extends AuthState {} final class SigningIn extends AuthState {}…
  8. 08

    Что делает if (value case pattern when condition) и что эта конструкция заменяет?

    Она сопоставляет value с паттерном, связывает названные в нём переменные и проверяет дополнительное булево условие (guard), и всё это в одном условии.

    class Session {  String? token;} // before: two reads of a mutable field and a `!`void sendOld(Session s) {…
  9. 09

    Что проверяет объектный паттерн вроде Cart(total: > 100) и какой код он выполняет?

    Сначала он проверяет value is Cart, затем вызывает геттер total и сопоставляет результат с > 100.

    class Item {  const Item(this.price);  final int price;} class Cart {…
  10. 10

    Как сопоставляется списочный паттерн вроде [first, ..., last] и во что обходится именованный rest-элемент ...final rest?

    Он проверяет, что значение является List, проверяет длину (ровно число элементов без rest-элемента и не меньше этого числа с ним) и читает элементы по индексу с обоих концов.

    String summary(List<String> names) => switch (names) {      [] => 'nobody',      [final only] => only,      [final a, final b] => '$a and $b',      [final first, ..., final last] => '$first … $last', // nothing copied    };…
  11. 11

    Как map-паттерн сопоставляется с декодированным JSON и что происходит с лишними или отсутствующими ключами?

    Map-паттерн проверяет, что значение является Map, затем ищет каждый перечисленный ключ и сопоставляет его значение с подпаттерном.

    String greet(Map<String, Object?> json) {  if (json      case {'type': 'user', 'name': final String name, 'age': final int age}) {    return '$name, $age'; // extra keys in json are ignored  }  if (json case {'type': 'user', 'name': final String name}) {…
  12. 12

    Что такое паттерны сравнения и логические паттерны и когда вместо них нужен guard when?

    Паттерны сравнения (relational) сравнивают значение с константой через ==, !=, <, <=, > или >=; логические паттерны объединяют паттерны через && и ||.

    const adult = 18;const retirement = 65; String bracket(int age) => switch (age) {      < 0 => 'invalid',      < adult => 'minor',…
  13. 13

    Что выведет эта программа и почему важен порядок веток switch?

    String describe(Object o) => switch (o) {      int n when n > 10 => 'big int',      int() => 'int',      num n => 'num $n',      String(isEmpty: true) => 'empty string',      String s => 'string $s',      _ => 'other',    }; void main() {  for (final v in [42, 7, 2.5, '', 'hi', true]) {    print(describe(v));  }}

    Программа выведет big int, int, num 2.5, empty string, string hi, other.

  14. 14

    Почему этот switch не компилируется, хотя в нём упомянут каждый подтип Result?

    sealed class Result {} final class Ok extends Result {  Ok(this.value);  final int value;} final class Err extends Result {  Err(this.message);  final String message;} String show(Result r) => switch (r) {      Ok(:final value) when value > 0 => 'positive $value',      Err(:final message) => 'error: $message',    };

    Единственная ветка Ok содержит guard when, а ветки с guard не учитываются при проверке исчерпываемости.

  15. 15

    Найдите баг: надпись о загрузке так и не появляется, а пока данные грузятся, пользователи видят «Something went wrong».

    sealed class LoadState {}final class Loading extends LoadState {}final class Loaded extends LoadState {  Loaded(this.items);  final List<String> items;}final class Failed extends LoadState {} String label(LoadState state) => switch (state) {      Loading => 'Loading…',      Loaded(:final items) => '${items.length} items',      _ => 'Something went wrong',    };

    Loading => не проверяет тип: голое имя типа в паттерне задаёт константный паттерн, который сравнивает состояние с объектом Type Loading через ==.

  16. 16

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

    class Product {  Product(this.name, this.price);  final String name;  final double price;} // `json` comes straight from `jsonDecode(response.body)`Product? parseProduct(Map<String, Object?> json) {  if (json case {'name': String name, 'price': double price}) {    return Product(name, price);  }  return null; // reported upstream as "invalid product"}

    Паттерн требует, чтобы price был double, но jsonDecode превращает 12 в int, и только 12.5 становится double.

  17. 17

    Какие возможности языка Dart 3.x, кроме записей и паттернов, вы использовали и что каждая из них заменила?

    Dart 3.0 принёс ещё и модификаторы классов вроде sealed и final; в следующих версиях появились extension types (3.3), разделители разрядов (3.6), wildcard-переменные _ (3.7), null-aware элементы коллекций (3.8), dot shorthands (3.10) и primary constructors (3.13).

    // Needs `sdk: ^3.13.0` in pubspec.yaml.enum Side { start, center, end } class Point(final int x, final int y); // 3.13: constructor and fields in the header extension type UserId(String value) {} // 3.3: a zero-cost typed wrapper…
  18. 18

    Что произойдёт, если добавить подтип в публичный sealed-класс, и как безопасно выкатить такое изменение?

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

  19. 19

    Во что обходятся записи и паттерны в рантайме: что выделяет память и какой код выполняется при сопоставлении?

    Сопоставление и деструктуризация сводятся к проверкам типов и чтению геттеров и ничего не выделяют; создание записи выделяет объект, как любой вызов конструктора.