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

#dart #lambda

Вопрос:

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

Ответ №1:

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

  1. Постоянство. Функции и static методы верхнего уровня являются константами времени компиляции. В отличие от этого, Dart в настоящее время не позволяет функциональным выражениям (лямбдам) когда-либо быть константами. Последствия включают в себя:
    • Оптимизируемость. Когда компилятор видит сайт вызова, он точно знает, что будет вызвано. Это позволяет ему выполнять оптимизацию (например, потенциально встраивать вызов функции).
    • Аргументы по умолчанию. Поскольку Dart не позволяет выражениям функций быть константами, их нельзя напрямую использовать в качестве аргументов по умолчанию. Например:
         int square(int x) => x * x;
        final cube = (int x) => x * x * x;
      
        void f([int Function(int) callback = square]) { // OK.
          print(callback(42));
        }
      
      
        void g([int Function(int) callback = cube]) { // ERROR: Default value
                                                      // must be constant.
          print(callback(42));
        }
       
    • Точки входа. Точки входа, такие как main и такие, как обратные вызовы, используемые для порождения Isolate s, также должны быть константами, и поэтому они могут быть объявлены только функциями или static методами верхнего уровня, а не лямбдами.
  2. Рекурсия. Объявленные функции немедленно связывают имя, но при инициализации переменной из выражения функции сначала необходимо оценить выражение. Порядок имеет значение, если выражение функции является самореферентным (т. Е. рекурсивным). Например, это не является законным:
     final f = (int x) {
      if (x > 0) {
        print(x);
        f(x - 1); // Error: Variable can't be referenced before it is declared.
      }
    };
     

    и вместо этого нужно было бы объявить переменную отдельно:

     late final void Function(int) f;
    f = (int x) {
      if (x > 0) {
        print(x);
        f(x - 1);
      }
    };
     

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

  3. Типы возвращаемых данных. В существующем синтаксисе нет прямого указания типа возвращаемого значения для лямбда-выражения, которое вместо этого полагается на вывод типа. Следовательно, было много раз, когда люди делали такие вещи, как:
     () {
      if (someCondition) {
        return someValue;
      }
    }
     

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

  4. Удобочитаемость. Использование лямбд в лучшем случае немного более подробно, практически без пользы:
     int square(int x) => x * x;
    
    var square = (int x) => x * x;
     

    и это еще хуже, если вы хотите объявить тип возвращаемого значения и предотвратить переназначение:

     final int Function(int) square = (x) => x * x;
     

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

Обратите внимание, что пункты о типах констант и возвращаемых данных не объясняют, почему язык был разработан таким образом. Возможно, что-то подобное const square = (int x) => x * x; должно быть законным, и можно было бы изобрести какой-то новый синтаксис для указания типа возвращаемого значения. (Я полагаю, что одной из причин против const лямбд было бы то, что это затруднило бы канонизацию. Дано:

 const foo = (int x) => x * x;
const bar = (int y) => y * y;
 

должно foo быть identical , чтобы bar ?) Реализация этих вещей не бесплатна, и в конечном счете для этого мало мотивации.

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

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

2. Большое вам спасибо за ваш обстоятельный ответ (и за ваш комментарий @lrn). То, что вы написали, имеет большой смысл. Я даже не понимал, что лямбды (или, по крайней мере, чистая лямбда-часть лямбды, привязанная к переменной) не могут указывать тип возвращаемого значения. Часть о постоянстве была для меня совершенно новой. Принимая во внимание эти два момента, ваш последний (Читаемость) имеет такой большой смысл. Ты определенно ответил на мой вопрос.

3. @JacobEmery Я добавил еще один пункт о рекурсии. Кроме того, если мой ответ помог вам, пожалуйста, поднимите голос и примите его. Спасибо!

4. @jamesdlin Я пытался поддержать тебя, но не смог, потому что у меня нет очков репутации. Поэтому я подумал, что тоже не могу принять ответы. Кстати, спасибо вам за замечание о рекурсии.