web-dev-qa-db-fra.com

Qu'est-ce qui a déclenché la popularité des fonctions lambda dans les langages de programmation grand public modernes?

Au cours des dernières années, les fonctions anonymes (fonctions lambda AKA) sont devenues une construction de langage très populaire et presque tous les langages de programmation principaux/traditionnels les ont introduits ou devraient être introduits dans une prochaine révision de la norme.

Pourtant, les fonctions anonymes sont un concept très ancien et très connu en mathématiques et en informatique (inventé par le mathématicien Alonzo Church vers 1936, et utilisé par le langage de programmation LISP depuis 1958, voir par exemple ici ) .

Alors pourquoi les langages de programmation traditionnels d'aujourd'hui (dont beaucoup sont nés il y a 15 à 20 ans) ne prennent-ils pas en charge les fonctions lambda depuis le tout début et ne les ont-ils introduits que plus tard?

Et qu'est-ce qui a déclenché l'adoption massive de fonctions anonymes au cours des dernières années? Y a-t-il un événement spécifique, une nouvelle exigence ou une technique de programmation qui a déclenché ce phénomène?

REMARQUE IMPORTANTE

Le point central de cette question est l'introduction de fonctions anonymes dans les langages modernes de flux principal (et donc, à quelques exceptions près, non fonctionnels). Notez également que les fonctions anonymes (blocs) sont présentes dans Smalltalk, qui n'est pas un langage fonctionnel, et que les fonctions nommées normales sont présentes même dans les langages procéduraux comme C et Pascal depuis longtemps.

Veuillez ne pas généraliser vos réponses en parlant de "l'adoption du paradigme fonctionnel et de ses avantages", car ce n'est pas le sujet de la question.

114
Giorgio

Il y a certainement une tendance notable vers la programmation fonctionnelle, ou du moins certains aspects de celle-ci. Certains des langages populaires qui à un moment donné ont adopté des fonctions anonymes sont C++ ( C++ 11 ), PHP ( PHP 5.3. ), C # ( C # v2. ), Delphi (depuis 2009), Objective C ( blocks ) while Java 8 apportera le support des lambdas au langage) Et il existe des langages populaires qui ne sont généralement pas considérés comme fonctionnels mais qui prennent en charge les fonctions anonymes depuis le début, ou du moins au début, l'exemple brillant étant JavaScript.

Comme pour toutes les tendances, essayer de rechercher un événement unique qui les a déclenchées est probablement une perte de temps, c'est généralement une combinaison de facteurs, dont la plupart ne sont pas quantifiables. Practical Common LISP , publié en 2005, peut avoir joué un rôle important en attirant une nouvelle attention au LISP en tant que langage pratique , comme pendant un certain temps, LISP était principalement une langue que vous rencontriez dans un cadre académique ou sur des marchés de niche très spécifiques. La popularité de JavaScript a peut-être également joué un rôle important en attirant une nouvelle attention sur les fonctions anonymes, comme l'explique munificent dans sa réponse .

Outre l'adoption de concepts fonctionnels à partir de langages polyvalents, il y a également un changement notable vers les langages fonctionnels (ou principalement fonctionnels). Des langues comme Erlang (1986), Haskell (1990), OCaml (1996), Scala (2003), F # (2005), Clojure (2007), et même des langages spécifiques à un domaine comme R (1993 ) semblent avoir fortement gagné en popularité après leur introduction.La tendance générale a attiré une attention nouvelle sur les langages fonctionnels plus anciens, comme Scheme (1975), et évidemment Common LISP.

Je pense que l'événement le plus important est l'adoption de la programmation fonctionnelle dans l'industrie. Je ne sais absolument pas pourquoi cela n'était pas le cas, mais il me semble qu'à un moment donné au début et au milieu des années 90, la programmation fonctionnelle a commencé à trouver sa place dans l'industrie, à commencer (peut-être) par Prolifération d'Erlang dans les télécommunications et adoption de Haskell dans l'aérospatiale et la conception matérielle .

Joel Spolsky a écrit un article de blog très intéressant, The Perils of JavaSchools , où il plaide contre la tendance (alors) des universités à favoriser Java par rapport à d'autres, peut-être plus) difficile à apprendre les langues. Bien que le billet de blog ait peu à voir avec la programmation fonctionnelle, il identifie un problème clé:

C'est là que réside le débat. Des années de whinging par des étudiants paresseux de CS comme moi, combinées à des plaintes de l'industrie sur le peu de diplômés de CS qui sortent des universités américaines, ont fait des ravages, et au cours de la dernière décennie, un grand nombre d'écoles par ailleurs parfaitement bonnes sont devenues 100% Java. C'est branché, les recruteurs qui utilisent "grep" pour évaluer les CV semblent l'aimer, et, surtout, il n'y a rien de bien difficile à propos de Java pour vraiment éliminer les programmeurs sans la partie du cerveau qui fait des pointeurs ou des récursions, donc les taux d'abandon sont plus bas, et les départements d'informatique ont plus d'étudiants et des budgets plus importants, et tout va bien.

Je me souviens encore combien je détestais LISP, quand je l'ai rencontrée pour la première fois pendant mes années universitaires. C'est définitivement une dure maîtresse, et ce n'est pas une langue où vous pouvez être immédiatement productif (enfin, du moins je ne pouvais pas). Par rapport à LISP, Haskell (par exemple) est beaucoup plus convivial, vous pouvez être productif sans trop d'effort et sans vous sentir comme un idiot complet, et cela pourrait également être un facteur important dans le passage à la programmation fonctionnelle.

Dans l'ensemble, c'est une bonne chose. Plusieurs langages polyvalents adoptent des concepts de paradigme qui auraient pu sembler obscurs à la plupart de leurs utilisateurs auparavant, et l'écart entre les principaux paradigmes se rétrécit.

Questions connexes:

Lectures complémentaires:

86
yannis

Je pense qu'il est intéressant de voir à quel point la popularité de la programmation fonctionnelle a été parallèle à la croissance et à la prolifération de Javascript. Javascript a beaucoup de fonctionnalités radicales le long du spectre de programmation fonctionnelle qui, au moment de sa création (1995), n'étaient pas très populaires parmi les langages de programmation traditionnels (C++/Java). Il a été soudainement injecté dans le courant dominant en tant que seul langage de programmation Web côté client. Tout à coup, beaucoup de programmeurs devaient simplement connaître Javascript et donc vous deviez connaître quelque chose des fonctionnalités du langage de programmation fonctionnel.

Je me demande à quel point les langages/fonctionnalités fonctionnels seraient populaires sans la montée soudaine de Javascript.

31
Doug T.

Les gestionnaires d'événements JavaScript et DOM signifiaient que des millions de programmeurs avaient pour en apprendre au moins un peu sur les fonctions de première classe afin de faire tout l'interactivité sur le web.

De là, c'est une étape relativement courte pour les fonctions anonymes. Parce que JavaScript ne ferme pas sur this, il vous encourage également fortement à en savoir plus sur les fermetures. Et puis vous êtes en or: vous comprenez les fonctions anonymes de première classe qui se ferment sur les étendues lexicales enfermées.

Une fois que vous êtes à l'aise avec cela, vous le voulez dans toutes les langues que vous utilisez.

27
munificent

Ce n'est certainement pas le facteur seulement, mais je soulignerai la popularité de Ruby. Je ne dis pas que cela est plus important que n'importe laquelle des six réponses déjà au tableau, mais je pense que beaucoup de choses se sont produites en même temps et qu'il est utile de toutes les énumérer.

Ruby n'est pas un langage fonctionnel et ses lambdas, prods et blocs semblent maladroits lorsque vous avez utilisé quelque chose comme ML, mais le fait est qu'il a popularisé la notion de mappage et de réduction à une génération de jeunes programmeurs fuyant Java et PHP pour les pâturages de hipper. Les lambdas dans plusieurs langues semblent être des mouvements défensifs plus que toute autre chose ("Restez! Nous les avons aussi !!)

Mais la syntaxe des blocs et la façon dont elle s'est intégrée à .each, .map, .reduce et ainsi de suite ont propulsé l'idée d'une fonction anonyme même si c'est vraiment une construction syntaxique qui se comporte comme une coroutine. Et la conversion facile en proc via & en fait un médicament de passerelle pour la programmation fonctionnelle.

Je soutiens que Ruby on Rails les programmeurs écrivant JavaScript étaient déjà enclins à faire les choses dans un style fonctionnel léger. Ajoutez à cela avec les blogs de programmeurs, l'invention de Reddit , hacker News et Stack Overflow à peu près au même moment, et les idées se sont répandues plus rapidement sur Internet qu'au temps des groupes de discussion.

TL; DR: Ruby, Rails, JavaScript, blogging et Reddit/Hacker News/Stack Overflow ont poussé les idées fonctionnelles vers un marché de masse, donc tout le monde les voulait dans les langues existantes pour éviter de nouvelles défections.

17
Reg Braithwaite

Comme Yannis l'a souligné, il existe un certain nombre de facteurs qui ont influencé l'adoption de fonctions de haut niveau dans des langues qui étaient auparavant sans. L'un des éléments importants qu'il n'a abordé que légèrement est la prolifération des processeurs multicœurs et, avec cela, le désir d'un traitement plus parallèle et simultané.

Le style de programmation fonctionnelle carte/filtrer/réduire est très convivial pour la parallélisation, permettant au programmeur d'utiliser facilement plusieurs cœurs, sans écrire de code de thread explicite.

Comme le note Giorgio, la programmation fonctionnelle ne se limite pas aux fonctions de haut niveau. Les fonctions, plus un schéma de programmation carte/filtre/réduction, et l'immuabilité sont au cœur de la programmation fonctionnelle. Ensemble, ces éléments constituent de puissants outils de programmation parallèle et simultanée. Heureusement, de nombreux langages prennent déjà en charge une certaine notion d'immutabilité et, même s'ils ne le font pas, les programmeurs peuvent traiter les choses comme immuables, ce qui permet aux bibliothèques et au compilateur de créer et de gérer des opérations asynchrones ou parallèles.

L'ajout de fonctions de haut niveau à un langage est une étape importante pour simplifier la programmation simultanée.

Mise à jour

J'ajouterai quelques exemples plus détaillés afin de répondre aux préoccupations notées par Loki.

Considérez le code C # suivant qui traverse une collection de widgets, créant une nouvelle liste de prix de widget.

List<float> widgetPrices;
    float salesTax = RetrieveLocalSalesTax();
foreach( Widget w in widgets ) {
    widgetPrices.Add( CalculateWidgetPrice( w, salesTax ) );
}

Pour une grande collection de widgets ou une méthode calculatrice intensivement calculatrice de widget (widget), cette boucle ne ferait pas bon usage des cœurs disponibles. Pour effectuer les calculs de prix sur différents cœurs, le programmeur devrait créer et gérer explicitement les threads, passer le travail et collecter les résultats ensemble.

Envisagez une solution une fois que les fonctions de haut niveau ont été ajoutées à C #:

var widgetPrices = widgets.Select( w=> CalculateWidgetPrice( w, salesTax ) );

La boucle foreach a été déplacée dans la méthode Select, cachant ses détails d'implémentation. Tout ce qui reste au programmeur est de dire à Select quelle fonction appliquer à chaque élément. Cela permettrait à l'implémentation Select d'exécuter les calculs en parallèle, gérant toutes les préoccupations de synchronisation et de gestion des threads sans l'implication du programmeur.

Mais, bien sûr, Select ne fait pas son travail en parallèle. C'est là que l'immuabilité entre en jeu. L'implémentation de Select ne sait pas que la fonction fournie (CalculateWidgets ci-dessus) n'a pas d'effets secondaires. La fonction pourrait changer l'état du programme en dehors de la vue de Select et de sa synchronisation, tout casser. Par exemple, dans ce cas, la valeur de salesTax pourrait être modifiée par erreur. Les langages fonctionnels purs offrent une immuabilité, de sorte que la fonction Select (map) peut savoir avec certitude qu'aucun état ne change.

C # résout ce problème en fournissant PLINQ comme alternative à Linq. Cela ressemblerait à:

var widgetPrices = widgets.AsParallel().Select(w => CalculateWidgetPrice( w, salesTax) );

Ce qui utilise pleinement tous les cœurs de votre système sans gestion explicite de ces cœurs.

13
Ben

Ayant été un peu impliqué dans l'histoire récente ici, je pense qu'un facteur était l'ajout de génériques à Java et .NET. Cela mène naturellement à Func <, > et d'autres abstractions de calcul fortement typées (tâche <>, async <> etc.)

Dans le monde .NET, nous avons ajouté ces fonctionnalités précisément pour prendre en charge FP. Cela a déclenché un ensemble de travaux de langage en cascade liés à la programmation fonctionnelle, en particulier C # 3.0, LINQ, Rx et F #. Cette progression a également influencé d'autres écosystèmes et se poursuit encore aujourd'hui en C #, F # et TypeScript.

Bien entendu, cela aide également Haskell à travailler chez MSR :)

Bien sûr, il y avait aussi beaucoup d'autres influences (JS certainement) et ces étapes ont été à leur tour influencées par beaucoup d'autres choses - mais l'ajout de génériques à ces langages a contribué à briser l'orthodoxie rigide OO de la fin des années 90 dans de grandes parties du monde du logiciel et a aidé à ouvrir la porte à la PF.

Don Syme

p.s. F # était 2003, pas 2005 - bien que nous dirions qu'il n'a pas atteint 1.0 avant 2005. Nous avons également fait un prototype Haskell.NET en 2001-02.

9
Don Syme

Je suis d'accord avec de nombreuses réponses ici, mais ce qui est intéressant, c'est que lorsque j'ai découvert les lambdas et que j'ai sauté dessus, ce n'était pour aucune des raisons mentionnées par d'autres.

Dans de nombreux cas, les fonctions lambda améliorent simplement la lisibilité de votre code. Avant lambdas, lorsque vous appelez une méthode qui accepte un pointeur de fonction (ou une fonction ou un délégué), vous devez définir le corps de cette fonction ailleurs, donc lorsque vous avez une construction "foreach", votre lecteur doit passer à une autre une partie du code pour voir exactement ce que vous prévoyez de faire avec chaque élément.

Si le corps de la fonction qui traite les éléments n'est que de quelques lignes, j'utiliserais une fonction anonyme car maintenant, lorsque vous lisez du code, la fonctionnalité reste inchangée, mais le lecteur n'a pas à sauter d'avant en arrière, l'implémentation entière est juste devant lui.

De nombreuses techniques de programmation fonctionnelle et de parallélisation pourraient être réalisées sans fonctions anonymes; il suffit d'en déclarer un régulier et de lui transmettre une référence chaque fois que vous en avez besoin. Mais avec lambdas, la facilité d'écriture du code et la facilité de lecture du code sont grandement améliorées.

9
DXM

D'après ce que je vois, la plupart des réponses se concentrent sur l'explication de la raison pour laquelle la programmation fonctionnelle a fait son grand retour et a fait son chemin dans le courant dominant. Je sentais cependant que cela ne répond pas vraiment à la question sur les fonctions anonymes en particulier, et pourquoi elles sont soudainement devenues si populaires.

Ce qui a vraiment gagné en popularité, ce sont fermetures . Étant donné que dans la plupart des cas, les fermetures sont des fonctions jetables transmises à des variables, il est évidemment logique d'utiliser une syntaxe de fonction anonyme pour celles-ci. Et en fait, dans certaines langues, c'est le seul moyen de créer une fermeture.

Pourquoi les fermetures ont-elles gagné en popularité? Parce qu'ils sont utiles dans la programmation événementielle, lors de la création de fonctions de rappel . C'est actuellement la façon d'écrire du code client JavaScript (en fait, c'est la façon d'écrire n'importe quel code GUI). C'est actuellement aussi la façon d'écrire du code back-end très efficace ainsi que du code système, car le code écrit dans le paradigme événementiel est généralement asynchrone et non bloquant . Pour le back-end, cela est devenu populaire comme solution à problème C10K .

4
vartec

Ce n'est pas censé être une réponse sérieuse, mais la question m'a rappelé un post humoristique cool de James Iry - ne brève, incomplète et surtout une mauvaise histoire des langages de programmation qui comprend la phrase suivante:

"Les Lambdas sont relégués à une obscurité relative jusqu'à ce que Java les rend populaires en ne les ayant pas."

4
yoniLavi

Si je peux ajouter mes 0,02 €, bien que je sois d'accord avec l'importance d'introduire JavaScript dans le concept, je pense que plus que la programmation simultanée, je blâmerais la mode actuelle de la programmation asynchrone. Lors des appels asynchrones (nécessaires avec les pages Web), les fonctions anonymes simples sont si évidemment utiles que chaque programmeur Web (c'est-à-dire chaque programmeur) devait se familiariser avec le concept.

1
mcepl

Je pense que la raison en est la prévalence croissante de la programmation simultanée et distribuée, où le cœur de la programmation orientée objet (encapsulant l'état changeant avec des objets) ne s'applique plus. Dans le cas d'un système distribué, car il n'y a is aucun état partagé (et les abstractions logicielles de ce concept sont fuyantes) et dans le cas d'un système simultané, car une synchronisation correcte de l'accès à l'état partagé a été prouvée encombrant et sujet aux erreurs. Autrement dit, l'un des principaux avantages de la programmation orientée objet ne s'applique plus à de nombreux programmes, ce qui rend le paradigme orienté objet beaucoup moins utile qu'il l'était auparavant.

En revanche, le paradigme fonctionnel n'utilise pas d'état mutable. Toute expérience que nous avons acquise avec les paradigmes et modèles fonctionnels est donc plus immédiatement transférable au calcul simultané et distribué. Et plutôt que de réinventer la roue, l'industrie emprunte désormais ces modèles et fonctionnalités linguistiques pour répondre à ses besoins.

1
meriton

call-by-name dans ALGOL 60 est un autre exemple vraiment ancien de quelque chose qui s'apparente à des fonctions/lambdas anonymes. Notez cependant que l'appel par nom est plus proche de passer des macros en tant que paramètres que de passer de vraies fonctions, et il est donc plus fragile/plus difficile à comprendre.

1
Stephen C

Voici l'ascendance au meilleur de ma connaissance.

  • 2005: Javascript a récemment ramené la programmation d'ordre supérieur avec lambdas dans le courant dominant. Dans des bibliothèques particulières comme nderscore.js et jquery . L'une des premières de ces bibliothèques était prototype.js qui précède jquery d'environ un an. Le prototype est basé sur le module Enumerable de Ruby, ce qui nous amène à…
  • 1996: Ruby's module énumérable s'est très évidemment inspiré du cadre de collecte de Smalltalk. Comme l'a mentionné Matz dans de nombreuses interviews, ce qui nous amène à…
  • 1980: Smalltalk utilise beaucoup de programmation d'ordre supérieur et fournit une API de collecte qui fait un usage intensif de programmation d'ordre supérieur (par exemple, classe Iterable de GNU Smalltalk ). Dans le code Smalltalk idiomatique, vous n'en trouverez aucune pour les boucles, mais uniquement des énumérations d'ordre élevé. Malheureusement, lorsque Java lorsque le cadre de collecte de Smalltalk a été porté sur Java en 1998, les énumérations d'ordre supérieur ont été omises. est la façon dont la programmation d'ordre supérieur a été progressivement abandonnée pour les dix prochaines années! Smalltalk a de nombreuses ancêtres, mais la question d'OP est pertinente LISP, ce qui nous amène à…
  • 1958: LISP, évidemment, a à sa base une programmation d'ordre supérieur.
0
akuhn