Pourquoi avons-nous besoin de «fonctions de rappel»?

16

Je lis le livre programming in Lua. Il a dit que

Les fermetures constituent un outil précieux dans de nombreux contextes. Comme nous l'avons vu, ils sont utiles comme arguments pour des fonctions d'ordre supérieur telles que le tri. Les fermetures sont précieuses pour les fonctions qui construisent également d'autres fonctions, comme notre exemple newCounter; ce mécanisme permet aux programmes Lua d'incorporer des techniques de programmation sophistiquées du monde fonctionnel. Les fermetures sont également utiles pour les fonctions de rappel. Un exemple typique se produit ici lorsque vous créez des boutons dans une boîte à outils GUI conventionnelle. Chaque bouton a une fonction de rappel à appeler lorsque l'utilisateur appuie sur le bouton; vous voulez que différents boutons fassent des choses légèrement différentes lorsqu'ils sont pressés. Par exemple, une calculatrice numérique a besoin de dix boutons similaires, un pour chaque chiffre. Vous pouvez créer chacun d'eux avec une fonction comme celle-ci:

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

Il semble que si j'appelle le digitButton, il retournera le action(cela créera une fermeture), donc je peux accéder au digitpassé digitButton.

Ma question est la suivante:

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


L'auteur a dit:

Dans cet exemple, nous supposons que Button est une fonction de boîte à outils qui crée de nouveaux boutons; label est l'étiquette du bouton; et action est la fermeture de rappel à appeler lorsque le bouton est enfoncé. Le rappel peut être appelé longtemps après que digitButton a fait sa tâche et après que la variable locale digit soit hors de portée, mais il peut toujours accéder à cette variable.

selon l'auteur, je pense qu'un exemple similaire ressemble à ceci:

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()

Donc, the callback can be called a long time after digitButton did its task and after the local variable digit went out of scope.

Lucas Li
la source

Réponses:

45

Guy 1 à Guy 2: hé mec, je veux faire quelque chose quand un utilisateur clique dessus, rappelez-moi quand ça se passe bien?

Guy 2 rappelle Guy 1 lorsqu'un utilisateur clique ici.

Florian Margaine
la source
1
J'aime beaucoup cette blague de rappel :-P
Lucas Li
6
Est ce juste moi? Je ne comprends pas comment cela explique pourquoi nous avons besoin de fonctions de rappel.
phant0m
2
C'est plus comme Guy 1 dit à Guy 2 "quand le moment est venu, fais ça". Guy 2 va sur sa journée et quand le moment est venu fait cela. Si vous vouliez dire que Guy 1 était l'appelant de Guy 2, alors c'est incorrect car Guy 2 ne rappelle pas Guy 1, il appelle la chose à faire. En fait, si Guy 1 est l'appelant de Guy 2, vous auriez une boucle dure sur vos mains dans ce scénario. Si vous vouliez que Guy 1 soit le rappel, c'est incorrect car le rappel n'a aucune idée de qui est Guy 2 et ne lui demande certainement pas d'appeler.
rism
Ceci est une horrible explication.
intérieur du
21

Non, il ne retournera jamais l'action. Le bouton l' exécutera lorsque l'utilisateur cliquera dessus. Et voilà la réponse. Vous avez besoin de fonctions de rappel lorsque vous souhaitez définir des actions qui doivent se produire dans un autre composant en réaction à un événement que vous ne contrôlez pas (la boucle d'événements, qui fait partie du système, exécutera une méthode du bouton qui à son tour s'exécutera l'action).

Jan Hudec
la source
Je ne suis pas d'accord avec toi. J'ai édité mon article et ajouté quelques codes. Je pense toujours que l'action n'est pas exécutée instantanément.
Lucas Li
2
Tim, vous avez raison - mais Jan aussi. "Le bouton l'exécutera lorsque l'utilisateur cliquera dessus", pas instantanément.
AlexanderBrevig
@AlexanderBrevig ouais, je pense toujours que la réponse de Jan est très utile pour moi. Cela me permet de savoir ce qu'est un rappel et pourquoi nous devons le rappeler.
Lucas Li
Le rappel n'est qu'un nom sale que quelqu'un a ajouté à une deuxième fonction qui fonctionne lorsque la première est déclenchée et peut-être en fonction d'un critère. Appelez votre fonction well_done () et n'aurait aucune différence. Le rappel est comme pour vérifier, vérifier est une séquence de messages, généralement deux, et le rappel est une sécuence de fonctions, généralement deux. Si vous avez enchaîné plus de 3 rappels, félicitations, vous aimez faire les choses à la dure.
m3nda
16

Je pense qu'un meilleur exemple pour l'utilisation des rappels est dans les fonctions asynchrones. Je ne peux pas parler pour Lua, mais en JavaScript, l'une des actions asynchrones les plus courantes consiste à contacter un serveur via AJAX.

L'appel AJAX est asynchrone, ce qui signifie que si vous faites quelque chose comme ceci:

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

le contenu du ifbloc ne fonctionnera pas correctement de manière fiable, et quand il le fera, c'est uniquement parce que la ajaxCall()fonction s'est terminée avant que l'exécution n'atteigne l' ifinstruction.

Cependant, les rappels éliminent ce problème en garantissant que l'appel asynchrone est terminé avant d'appeler la fonction requise. Ainsi, votre code se changerait en quelque chose comme ceci:

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

ajaxCall(ajaxCallback);

Le but de ceci est de vous permettre de faire des choses comme collecter des données, sans interférer avec d'autres choses, comme dessiner l'interface. Si la collecte de données était synchrone, l'interface ne répondrait plus pendant que l'application attendait d'obtenir les données. Une interface qui ne répond pas est très mauvaise pour l'expérience utilisateur, car la plupart des utilisateurs penseront que l'application a "planté" et essaieront de terminer le processus.

Pour voir cela en action, il vous suffit de consulter n'importe quel site Web qui effectue n'importe quel type de mise à jour sans recharger la page entière. Twitter, LinkedIn et les sites StackExchange en sont de bons exemples. Le "défilement sans fin" des flux de Twitter et les notifications de SE (pour les notifications des utilisateurs et les notifications qu'une question a une nouvelle activité) sont alimentés par des appels asynchrones. Lorsque vous voyez un spinner quelque part, cela signifie que la section a effectué un appel asynchrone et attend que cet appel se termine, mais vous pouvez interagir avec d'autres choses sur l'interface (et même effectuer d'autres appels asynchrones). Une fois cet appel terminé, il réagira et mettra à jour l'interface en conséquence.

Shauna
la source
12

Vous avez posé la question

Pourquoi avons-nous besoin de "fonctions de rappel"?

J'essaierai d'y répondre de manière concise et claire (si j'échoue à cela, veuillez vous référer au lien wiki au bas de cette réponse).

Les rappels sont utilisés pour reporter l'implémentation spécifique de quelque chose au dernier moment possible.

L'exemple de bouton en est une bonne illustration. Supposons que vous vouliez avoir un bouton dans votre application qui imprime une page sur l'imprimante, nous pourrions imaginer un monde où vous auriez à coder un tout nouveauPrinterButton classe pour ce faire.

Heureusement, nous avons la notion de callbacks (héritage et utilisation du modèle de modèle est également une sorte de sémantique de rappel), nous n'avons donc pas besoin de le faire.

Au lieu de réimplémenter chaque aspect d'un bouton, ce que nous faisons est d'ajouter à l'implémentation du bouton. Le Buttona le concept de faire quelque chose quand il est pressé. Nous lui disons simplement ce qu'est ce quelque chose.

Ce mécanisme d'injection de comportement dans les frameworks est appelé callbacks.

Exemple:

Injectons un certain comportement dans un bouton HTML:

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

Dans ce cas, onclickpointe vers un rappel, qui pour cet exemple est la print()méthode.


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

AlexanderBrevig
la source
Merci, après avoir lu votre illustration, je vais lire le wiki dont les exemples ne sont que des rappels synchrones.
Lucas Li
dire à une fonction la réponse à chaque fois qu'elle a un problème et que vous devez écouter les problèmes; enseigner une fonction pour résoudre un problème, il prend soin de lui-même.
Joe
8

Pourquoi avons-nous besoin de fonctions de rappel? à quelles situations puis-je l'appliquer?

Les fonctions de rappel sont très utiles dans la programmation événementielle. Ils vous permettent de configurer votre programme de telle manière que les événements déclenchent le code correct. Ceci est très fréquent dans les programmes avec GUI où les utilisateurs peuvent cliquer sur n'importe quel élément de l'interface utilisateur (tels que les boutons ou les éléments de menu) et le code approprié sera exécuté.

FrustratedWithFormsDesigner
la source
1
+ C'est pour ça que les rappels sont bons, mais je déteste avoir à les écrire :-)
Mike Dunlavey
Quand je n'étais qu'un étudiant de première année, mon professeur nous a appris à programmer en MFC, mais il ne nous a jamais dit ce qu'est le rappel :-(
Lucas Li
2

Que se passe-t-il sans rappels:

Guy 1: D'accord, j'attends que cette chose arrive. (sifflets, pouces twiddles)

Guy 2: Bon sang! Pourquoi Guy 1 ne me montre-t-il pas des trucs? Je veux voir des choses se passer !! Où est mon équipement? Comment peut-on faire quoi que ce soit ici?

Jim
la source
1

Tout d'abord, le terme rappel fait référence à une fermeture qui est utilisée pour quelque chose.

Par exemple, supposons que vous créez une fonction de fermeture et que vous la stockiez simplement dans une variable. Ce n'est pas un rappel, car il n'est utilisé pour rien.

Mais supposons que vous créiez une fermeture et la stockiez quelque part où elle sera appelée quand quelque chose se passera. Il est maintenant appelé un rappel.

Habituellement, les rappels sont créés par différentes parties du programme que les parties qui les appellent. Il est donc facile d'imaginer que certaines parties du programme "rappellent" les autres parties.

Autrement dit, les rappels permettent à une partie du programme de dire à une autre partie de faire quelque chose (n'importe quoi) quand quelque chose se produit.

Quant aux variables maintenues en vie en raison de leur utilisation dans une fermeture, c'est une fonctionnalité complètement différente appelée upvalues ​​(alias "prolonger la durée de vie des variables locales" parmi ceux qui ne parlent pas Lua). Et c'est aussi très utile.

Jonathan Graef
la source
oui, Extending the lifetime of local variablesc'est aussi dire le terme upvalue.
Lucas Li
Hum ... intéressant. Le terme upvalue semble être spécifique à Lua, bien qu'une recherche rapide sur Google montre qu'il a parfois été utilisé pour décrire d'autres langues. Je vais l'ajouter à ma réponse.
Jonathan Graef
1

Les rappels existent depuis les premiers jours de Fortran, au moins. Par exemple, si vous avez un solveur ODE (équation différentielle ordinaire), tel qu'un solveur Runge-Kutta, il pourrait ressembler à ceci:

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

Il permet à l'appelant de personnaliser le comportement du sous-programme en lui transmettant une fonction spéciale à appeler si nécessaire. Cela permet au solveur ODE d'être utilisé pour simuler une grande variété de systèmes.

Mike Dunlavey
la source
1

Je suis tombé sur cela et je voulais apporter une réponse plus à jour ...

Les fonctions de rappel nous permettent de faire quelque chose avec les données ultérieurement , permettant au reste de notre code de s'exécuter, au lieu de l'attendre. Le code asynchrone nous permet ce luxe d'exécuter quoi que ce soit plus tard . L'exemple le plus lisible de fonctions de rappel que j'ai rencontré est Promises in JavaScript. Dans l'exemple ci-dessous chaque fois que vous voyez la fonction (résultat) ou (nouveauRésultat) ou (finalRésultat) ... ce sont des fonctions de rappel. Le code entre crochets est exécuté une fois que les données reviennent du serveur. Ce n'est qu'à ce stade qu'il serait judicieux d'exécuter ces fonctions puisque maintenant les données dont elles ont besoin sont disponibles.

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);

le code est extrait de ... https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

Espérons que cela aide quelqu'un. C'est ce qui m'a aidé à comprendre les rappels :)

NRV
la source
1
cela ne semble pas offrir quoi que ce soit de substantiel par rapport aux points soulevés et expliqués dans les 9 réponses précédentes
moucher
1
Peut-être pour vous, mais c'est ce qui m'a vraiment permis de comprendre les rappels, alors j'ai pensé partager. OMI, la lisibilité de l'exemple est ce qui en vaut la peine. Je suis sûr que nous pouvons convenir que la lisibilité du code va un long chemin, en particulier pour les débutants.
NRV
-2

Je ne connais pas lua mais dans les méthodes de rappel générales, certains aiment le multithreading. Par exemple, dans la programmation de développement d'applications mobiles, la plupart des applications fonctionnent comme l'envoi de requêtes au serveur et le jeu avec l'interface utilisateur avec les données fournies par le serveur. Lorsque l'utilisateur envoie une demande au serveur, il faudra du temps pour obtenir une réponse du serveur, mais pour le meilleur UX UI ne doit pas rester bloqué.

Pour cette raison, nous utilisons plusieurs threads pour effectuer des opérations parallèles. Lorsque nous recevons la réponse du serveur, nous devons mettre à jour l'interface utilisateur. nous devons informer de ce fil pour mettre à jour. du monde fonctionnel. Ce type d'appels de fonction est appelé méthode de rappel. Lorsque vous appelez ces méthodes, le contrôle doit avoir retourné au thread principal. Par exemple, les méthodes de rappel sont des blocs de l'objectif-C.

AMohan
la source
oui, ce que vous avez dit est conforme à ma compréhension après avoir lu le livre.
Lucas Li
2
Les méthodes de rappel n'ont rien à voir avec le multithreading. Les méthodes de rappel sont simplement des pointeurs de fonction (délégués). Les threads concernent le changement / l'utilisation du contexte CPU. Un contraste très simplifié serait que le threading concerne l'optimisation du processeur pour UX tandis que les rappels concernent le changement de mémoire pour le polymorphisme. Le fait que les threads puissent exécuter des rappels ne signifie pas que les threads et les rappels sont similaires. Les threads exécutent également des méthodes statiques sur les classes statiques, mais les types de thread et statique ne sont pas similaires.
rism