Как изменить внутренние переменные функции во время выполнения и передать это другой функции?

#dart

#dart

Вопрос:

Функции в Dart являются объектами первого класса, что позволяет передавать их другим объектам или функциям.

 void main() {
  
  var shout = (msg) => '  ${msg.toUpperCase()} ';
  
  print(shout("yo"));
}
  

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

 Function add(int input) {
  return add   2;
}
  

Если бы я хотел сделать функцию общей функцией добавления, то я бы сделал:

 Function add(int input, int increment) {
  return add   increment;
}
  

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

Ответ №1:

Ответ, похоже, заключается в использовании лексического замыкания.

Отсюда:https://dart.dev/guides/language/language-tour#built-in-types

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

Функции могут закрываться по переменным, определенным в окружающих областях. В следующем примере makeAdder() фиксирует переменную addBy. Куда бы ни направлялась возвращаемая функция, она запоминает addBy.

 /// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy   i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}
  

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

Ответ №2:

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

Последнее просто:

 int Function(int) makeAdder(int increment) => (int value) => value   increment;
...
   foo(makeAdder(1));  // Adds 1.
   foo(makeAdder(4));  // Adds 2.
  

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

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

Глобальные переменные просты:

 int increment = 1;
int globalAdder(int value) => value   increment;

...
  foo(globalAdd);  // Adds 1.
  increment = 2;
  foo(globalAdd);  // Adds 2.
  

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

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

 class MakeAdder {
  int increment = 1;
  int instanceAdd(int value) => value   increment;
}
...
var makeAdder = MakeAdder();
var adder = makeAdder.instanceAdd;
...
  foo(adder);  // Adds 1.
  makeAdder.increment = 2;
  foo(adder);  // Adds 2.
  

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

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

 int Function(int) makeAdder(void Function(void Function(int)) setIncrementCallback) {
  var increment = 1;
  setIncrementCallback((v) {
    increment = v;
  });
  return (value) => value   increment;
}
...
void Function(int) setIncrement;
int Function(int) localAdd = makeAdder((inc) { setIncrement = inc; });
...
 foo(localAdd);  // Adds 1.
 setIncrement(2);
 foo(localAdd);  // Adds 2.
  

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

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

Ответ №3:

Использование приложения с частичной функцией

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

Если у вас есть что-то вроде:

 int add(int input, int increment) => input   increment;
  

и хотите передать это другой функции, которая ожидает предоставления меньшего количества аргументов:

 int foo(int Function(int input) applyIncrement) => applyIncrement(10);
  

тогда вы могли бы сделать:

 foo((input) => add(input, 2); // `increment` is fixed to 2
foo((input) => add(input, 4); // `increment` is fixed to 4
  

Использование вызываемых объектов

Другим подходом было бы создать вызываемый объект:

 class Adder {
  int increment = 0;

  int call(int input) => input   increment;
}
  

что можно было бы использовать с той же foo функцией выше:

 var adder = Adder()..increment = 2;
print(foo(adder)); // Prints: 12

adder.increment = 4;
print(foo(adder)); // Prints: 14