Зачем нам нужны «функции обратного вызова»?

16

Я читаю книгу programming in Lua. Он сказал, что

Закрытия предоставляют ценный инструмент во многих контекстах. Как мы уже видели, они полезны в качестве аргументов для функций высшего порядка, таких как sort. Замыкания полезны для функций, которые также создают другие функции, как, например, наш пример newCounter; Этот механизм позволяет программам Lua включать сложные методы программирования из функционального мира. Замыкания также полезны для функций обратного вызова. Типичный пример здесь происходит, когда вы создаете кнопки в обычном инструментарии GUI. Каждая кнопка имеет функцию обратного вызова, которая вызывается, когда пользователь нажимает кнопку; Вы хотите, чтобы разные кнопки делали немного разные вещи при нажатии. Например, цифровому калькулятору нужно десять одинаковых кнопок, по одной для каждой цифры. Вы можете создать каждый из них с помощью такой функции:

function digitButton (digit)
  return Button{label = tostring(digit),
                action = function ()
                  add_to_display(digit)
                  end}
end

Кажется, что если я вызову digitButton, он вернет action(это создаст замыкание), поэтому я могу получить доступ к digitпереданному digitButton.

Мой вопрос таков:

Why we need call back functions? what situations can I apply this to?


Автор сказал:

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

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

function Button(t)
  -- maybe you should set the button here
  return t.action -- so that you can call this later
end

function add_to_display(digit)
  print ("Display the button label: " .. tostring(digit))
end

function digitButton(digit)
  return Button{label = tostring(digit),
                action = function ()
                           add_to_display(digit)
                           end}
end

click_action = digitButton(10)
click_action()

Таким образом, the callback can be called a long time after digitButton did its task and after the local variable digit went out of scope.

Лукас Ли
источник

Ответы:

45

Парень 1 - Парень 2: эй, чувак, я хочу сделать что-нибудь, когда пользователь нажмет там, перезвонить мне, когда это произойдет, хорошо?

Парень 2 перезванивает Парню 1, когда пользователь нажимает здесь.

Флориан Маргейн
источник
1
Мне очень нравится эта обратная шутка :-P
Лукас Ли
6
Это только я? Я не понимаю, как это объясняет, почему нам нужны функции обратного вызова.
phant0m
2
Это больше похоже на то, как Гай 1 говорит Гай 2 «когда придет время, сделай это». Парень 2 идет о своем дне, и когда наступит подходящий момент, он это сделает. Если вы хотели, чтобы Гай 1 был вызывающим Гая 2, то это неверно, так как Гай 2 не перезванивает Гая 1, он призывает к действию. Фактически, если Guy 1 является вызывающим абонентом Guy 2, в этом сценарии у вас будет сложная петля. Если вы подразумевали, что Guy 1 будет перезвонить, то это неверно, поскольку колбэк не знает, кто такой Guy 2, и, конечно, не просит его перезвонить.
RISM
Это ужасное объяснение.
insidesin
21

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

Ян Худек
источник
Я не согласен с тобой. Я отредактировал свой пост и добавил несколько кодов. Я все еще думаю, что действие не выполняется мгновенно.
Лукас Ли
2
Тим, ты прав - но Ян тоже. «Кнопка выполнит ее, когда пользователь нажмет на нее», а не мгновенно.
AlexanderBrevig
@AlexanderBrevig да, я все еще думаю, что ответ Яна очень полезен для меня. Это позволяет мне знать, что перезвонит и почему нам нужно перезвонить.
Лукас Ли
Обратный вызов - это просто грязное имя, которое кто-то добавил во вторую функцию, которая работает при срабатывании первой и может быть основана на критериях. Вызовите свою функцию well_done () и не будете иметь разницы. Обратный вызов похож на Verify, verify - это последовательность сообщений, обычно два, а callback - последовательность функций, обычно два. Если вы заковали более 3-х колбэков, поздравляю, вам нравится делать вещи трудным путем.
m3nda
16

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

Вызов AJAX является асинхронным, то есть если вы делаете что-то вроде этого:

var ajax = ajaxCall();
if(ajax == true) {
  // Do something
}

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

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

function ajaxCallback(response) {   
  if(response == true) {
    // Do something   
  }
}

ajaxCall(ajaxCallback);

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

Чтобы увидеть это в действии, вам просто нужно взглянуть на любой веб-сайт, который выполняет какие-либо обновления без перезагрузки всей страницы. Twitter, LinkedIn и сайты StackExchange являются хорошими примерами. «Бесконечная прокрутка» каналов в Твиттере и уведомления SE (как для пользовательских уведомлений, так и для уведомлений о том, что вопрос имеет новую активность) приводятся в действие асинхронными вызовами. Когда вы где-то видите спиннер, это означает, что секция выполнила асинхронный вызов и ожидает его завершения, но вы можете взаимодействовать с другими объектами интерфейса (и даже делать другие асинхронные вызовы). Как только этот вызов завершится, он отреагирует и обновит интерфейс соответствующим образом.

Shauna
источник
12

Вы задали вопрос

Почему нам нужны «функции обратного вызова»?

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

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

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

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

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

Этот механизм внедрения поведения в рамки называется обратными вызовами.

Пример:

Давайте добавим некоторое поведение в кнопку HTML:

<button onclick="print()">Print page through a callback to the print() method</button>

В этом случае onclickуказывает на обратный вызов, который для этого примера является print()методом.


http://en.wikipedia.org/wiki/Callback_(computer_programming)

AlexanderBrevig
источник
Спасибо, после прочтения вашей иллюстрации, я иду читать вики, примеры которых - только синхронные обратные вызовы.
Лукас Ли
сообщайте функции ответ каждый раз, когда у нее возникают проблемы, и вам нужно выслушивать проблемы; научить функцию решать проблему, которая заботится о себе.
Джо
8

Почему нам нужны функции обратного вызова? в каких ситуациях я могу применить это?

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

FrustratedWithFormsDesigner
источник
1
+ Вот для чего нужны обратные вызовы, но я ненавижу писать их :-)
Майк Данлавей,
Когда я был только новичком, мой учитель учил нас программировать в MFC, но он никогда не говорил нам, что такое обратный вызов :-(
Лукас Ли
2

Что происходит без обратных вызовов:

Парень 1: Хорошо, я жду, когда это произойдет. (свистит, вертит пальцами)

Парень 2: Черт возьми! Почему Гай 1 не показывает мне вещи? Я хочу видеть, что происходит !! Где мои вещи? Как кто-нибудь должен делать что-то здесь?

Джим
источник
1

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

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

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

Обычно обратные вызовы создаются разными частями программы, а не частями, которые их вызывают. Поэтому легко представить, что некоторые части программы «перезванивают» другим частям.

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

Что касается переменных, которые поддерживаются живыми из-за использования в замыкании, это совершенно другая функция, называемая upvalues ​​(то есть «продление времени жизни локальных переменных» среди тех, кто не говорит на Lua). И это тоже весьма полезно.

Джонатан Грайф
источник
да, Extending the lifetime of local variablesэто тоже сказать термин upvalue.
Лукас Ли
Хм, интересно. Кажется, что термин upvalue специфичен для Lua, хотя быстрый поиск в Google показывает, что он иногда использовался для описания других языков. Я добавлю это к моему ответу.
Джонатан Грайф
1

Обратные вызовы были вокруг с первых дней Фортрана, по крайней мере. Например, если у вас есть решатель ODE (обыкновенного дифференциального уравнения), такой как решатель Рунге-Кутты, он может выглядеть так:

subroutine rungekutta(neq, t, tend, y, deriv)
  integer neq             ! number of differential equations
  double precision t      ! initial time
  double precision tend   ! final time
  double precision y(neq) ! state vector
  ! deriv is the callback function to evaluate the state derivative
  ...
  deriv(neq, t, y, yprime) ! call deriv to get the state derivative
  ...
  deriv(neq, t, y, yprime) ! call it several times
  ...
end subroutine

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

Майк Данлавей
источник
1

Наткнулся на это и хотел предоставить более актуальный ответ ...

Функции обратного вызова позволяют нам что-то делать с данными позднее , позволяя выполнять остальную часть нашего кода вместо того, чтобы ждать его. Асинхронный код позволяет нам эту роскошь выполнить что-нибудь позже . Наиболее читаемый пример функций обратного вызова, с которыми я сталкивался, - это обещания в JavaScript. В приведенном ниже примере каждый раз, когда вы видите функцию (результат) или (newResult) или (finalResult) ... это функции обратного вызова. Код в их фигурных скобках выполняется, как только данные возвращаются с сервера. Только на этом этапе имеет смысл выполнять эти функции, поскольку теперь необходимые данные доступны.

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

код взят из ... https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

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

NRV
источник
1
это, кажется, не предлагает ничего существенного по сравнению с замечаниями, сделанными и объясненными в предыдущих 9 ответах
комнат
1
Может быть, для вас, но это то, что действительно сделало обратные вызовы понятными для меня, поэтому я думал, что поделюсь. ИМО читаемость этого примера, что делает его стоящим. Я уверен, что мы можем согласиться с тем, что читаемость кода имеет большое значение, особенно для начинающих.
NRV
-2

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

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

AMohan
источник
да, то, что ты сказал, согласуется с моим пониманием после прочтения книги.
Лукас Ли
2
Методы обратного вызова не похожи на многопоточность. Методы обратного вызова - это просто указатели на функции (делегаты). Потоки о переключении / использовании контекста процессора. Сильно упрощенный контраст состоит в том, что многопоточность - это оптимизация процессора для UX, тогда как обратные вызовы - это переключение памяти для полиморфизма. То, что потоки могут выполнять обратные вызовы, не означает, что потоки и обратные вызовы похожи. Потоки также выполняют статические методы в статических классах, но потоки и статические типы не похожи.
RISM