Article de reference

Fonction de première classe

En informatique , un langage de programmation est dit posséder des fonctions de première classe s'il traite les fonctions comme des objets de première classe . Cela signifie que...

En informatique , un langage de programmation est dit posséder des fonctions de première classe s'il traite les fonctions comme des objets de première classe . Cela signifie que le langage permet de passer des fonctions en arguments à d'autres fonctions, de les renvoyer comme valeurs depuis d'autres fonctions, et de les affecter à des variables ou de les stocker dans des structures de données. Certains théoriciens des langages de programmation exigent également la prise en charge des fonctions anonymes (littéraux de fonction). Dans les langages possédant des fonctions de première classe, les noms des fonctions n'ont aucun statut particulier ; ils sont traités comme des variables ordinaires de type fonction . Le terme a été forgé par Christopher Strachey dans le contexte des « fonctions comme objets de première classe » au milieu des années 1960.

Les fonctions de première classe sont essentielles à la programmation fonctionnelle , où l'utilisation de fonctions d'ordre supérieur est une pratique courante. Un exemple simple de fonction d'ordre supérieur est la fonction `map` , qui prend comme arguments une fonction et une liste, et renvoie la liste obtenue en appliquant la fonction à chaque élément de la liste. Pour qu'un langage prenne en charge `map` , il doit permettre de passer une fonction en argument.

Il existe certaines difficultés d'implémentation liées au passage de fonctions en arguments ou à leur retour en tant que résultats, notamment en présence de variables non locales introduites dans les fonctions imbriquées et anonymes . Historiquement, ces difficultés étaient appelées « problèmes funarg » , du terme dérivé de « function argument » (argument de fonction ). Dans les premiers langages impératifs, ces problèmes étaient contournés soit en n'autorisant pas les fonctions comme types de résultats (par exemple, ALGOL 60 , Pascal ), soit en omettant les fonctions imbriquées et donc les variables non locales (par exemple, C ). Le langage fonctionnel Lisp, à ses débuts , adoptait l'approche de la portée dynamique : les variables non locales font référence à la définition la plus proche de cette variable au point d'exécution de la fonction, et non à son emplacement de définition. La prise en charge adéquate des fonctions de première classe à portée lexicale a été introduite dans Scheme et nécessite de gérer les références aux fonctions comme des fermetures plutôt que comme de simples pointeurs de fonction , ce qui rend le ramasse-miettes indispensable.Haskell ) par rapport à un langage impératif où les fonctions sont des citoyens de seconde classe ( C ).

Fonctions d'ordre supérieur : passage de fonctions comme arguments

Haskell :

b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs "
map :: ( a -> b ) -> [ a ] ​​-> [ b ] map f [] = [] map f ( x : xs ) = f x : map f xs

Les langages où les fonctions ne sont pas de première classe permettent souvent d'écrire des fonctions d'ordre supérieur grâce à des fonctionnalités telles que les pointeurs de fonction ou les délégués . En C :

des listes , tandis que l'exemple C opère sur des tableaux . Ces deux types de structures de données composées sont les plus naturels dans leurs langages respectifs, et faire en sorte que l'exemple C opère sur des listes chaînées l'aurait rendu inutilement complexe. Cela explique également pourquoi la fonction C nécessite un paramètre supplémentaire (indiquant la taille du tableau). La fonction C met à jour le tableau sur place , sans retourner de valeur, tandis qu'en Haskell, les structures de données sont persistantes (une nouvelle liste est retournée, l'ancienne étant conservée). L'exemple Haskell utilise la récursivité pour parcourir la liste, tandis que l'exemple C utilise l'itération . Là encore, il s'agit de la manière la plus naturelle d'exprimer cette fonction dans les deux langages, mais l'exemple Haskell aurait facilement pu être exprimé en termes de pliage et l'exemple C en termes de récursivité. Enfin, la fonction Haskell possède un type polymorphe ; comme celui-ci n'est pas pris en charge par C, nous avons fixé toutes les variables de type à la constante de type `int` int.

Fonctions anonymes et imbriquées

de code répétitif . Si cette fonctionf avait été imbriquée , nous aurions rencontré le même problème, et c'est pourquoi elles ne sont pas prises en charge en C.

Fonctions d'ordre supérieur : renvoyer des fonctions comme résultats

Lorsqu'on retourne une fonction, on retourne en réalité sa fermeture. Dans l'exemple en C, toutes les variables locales capturées par la fermeture sortiront de la portée une fois que l'on aura quitté la fonction qui a créé la fermeture. Forcer la fermeture ultérieurement entraînera un comportement indéfini, pouvant corrompre la pile. C'est ce qu'on appelle le problème de l'argument funarg ascendant .

Affecter des fonctions à des variables

L'affectation de fonctions à des variables et leur stockage dans des structures de données (globales) peuvent potentiellement souffrir des mêmes difficultés que le retour de fonctions.

[Integer]] f = let a = 3 b = 1 in [map (\\x -> a * x + b), map (\\x -> b * x + a)] "
f :: [[ Entier ] -> [ Entier ]] f = soit a = 3 b = 1 dans [ map ( \ x -> a * x + b ), map ( \ x -> b * x + a )]

Égalité des fonctions

Puisqu'il est possible de tester l'égalité de la plupart des littéraux et des valeurs, il est naturel de se demander si un langage de programmation peut prendre en charge le test d'égalité des fonctions. À y regarder de plus près, cette question apparaît plus complexe et il convient de distinguer plusieurs types d'égalité de fonctions :

égalité extensionnelle
Deux fonctions f et g sont dites extensionnellement égales si leurs valeurs de sortie sont identiques pour toutes les entrées (∀ x . f ( x ) = g ( x )). Selon cette définition de l'égalité, par exemple, deux implémentations quelconques d'un algorithme de tri stable , comme le tri par insertion et le tri fusion , seraient considérées comme égales. Déterminer l'égalité extensionnelle est indécidable en général et souvent impossible, même pour les fonctions à domaine fini. C'est pourquoi aucun langage de programmation n'implémente l'égalité des fonctions comme une égalité extensionnelle.
égalité tensionnelle
En vertu de l'égalité intensionnelle, deux fonctions f et g sont considérées comme égales si elles possèdent la même structure interne. Ce type d'égalité peut être implémenté dans les langages interprétés en comparant le code source des corps de fonction (comme dans Interpreted Lisp 1.5) ou le code objet dans les langages compilés . L'égalité intensionnelle implique l'égalité extensionnelle (à condition que les fonctions soient déterministes et ne possèdent pas d'entrées cachées, telles qu'un compteur de programme ou une variable globale mutable ).
la transparence référentielle et n'est donc pas prise en charge par les langages purs , tels que Haskell.

Théorie des types

théorie des types , le type des fonctions acceptant des valeurs de type A et renvoyant des valeurs de type B peut s'écrire AB ou B → A. Dans la correspondance de Curry-Howard , les types de fonctions sont liés à l'implication logique ; l'abstraction lambda correspond à la levée d'hypothèses et l'application de fonction correspond au modus ponens . Outre le cas classique des fonctions de programmation, la théorie des types utilise également des fonctions de première classe pour modéliser les tableaux associatifs et les structures de données similaires .

Dans les approches catégorielles de la programmation, l'existence de fonctions de première classe correspond à l' hypothèse de catégorie fermée . Par exemple, le lambda-calcul simplement typé correspond au langage interne des catégories cartésiennes fermées .

Soutien linguistique

Les langages de programmation fonctionnelle, tels qu'Erlang , Scheme , ML , Haskell , F# et Scala , possèdent tous des fonctions de première classe. Lors de la conception de Lisp , l'un des premiers langages fonctionnels, tous les aspects des fonctions de première classe n'étaient pas encore bien compris, ce qui a conduit à une portée dynamique des fonctions. Les dialectes plus récents de Scheme et Common Lisp possèdent quant à eux des fonctions de première classe à portée lexicale.

De nombreux langages de script, notamment Perl , Python , PHP , Lua , Tcl /Tk, JavaScript et Io , possèdent des fonctions de première classe.

Pour les langages impératifs, il convient de distinguer Algol de ses descendants tels que Pascal, la famille C traditionnelle et les variantes modernes avec ramasse-miettes. La famille Algol autorise les fonctions imbriquées et les fonctions d'ordre supérieur prenant une fonction comme argument, mais pas les fonctions d'ordre supérieur renvoyant une fonction (à l'exception d'Algol 68, qui le permet). Cette limitation s'explique par l'impossibilité de gérer les variables non locales lorsqu'une fonction imbriquée est renvoyée (Algol 68 générant des erreurs d'exécution dans ce cas).

La famille de langages C autorisait à la fois le passage de fonctions en arguments et leur retour en résultats, mais évitait tout problème en ne prenant pas en charge les fonctions imbriquées. (Le compilateur gcc les autorise en tant qu'extension.) L'utilité du retour de fonctions résidant principalement dans la possibilité de retourner des fonctions imbriquées ayant capturé des variables non locales, plutôt que des fonctions de premier niveau, ces langages ne sont généralement pas considérés comme possédant des fonctions de première classe.

Les langages impératifs modernes prennent souvent en charge le ramasse-miettes, ce qui rend possible l'implémentation de fonctions de première classe. Ces fonctions n'ont généralement été prises en charge que dans les versions plus récentes du langage, notamment C# 2.0 et l'extension Blocks d'Apple pour C, C++ et Objective-C. C++11 a introduit la prise en charge des fonctions anonymes et des fermetures, mais en raison de l'absence de ramasse-miettes, il convient d'être particulièrement vigilant quant au traitement des variables non locales renvoyées par les fonctions (voir ci-dessous).

LangueFonctions d'ordre supérieurFonctions imbriquéesVariables non localesNotes
ArgumentsRésultatsNomméAnonymeFermeturesDemande partielle
Famille AlgolALGOL 60Avoir des types de fonctions .
ALGOL 68PascalAdaOberonDelphesFamille CCBlocs ).Blocs ).des pointeurs de fonction .
C++des objets fonction . (Voir aussi ci-dessous.)

Application partielle explicite possible avec std::bind.

C#des délégués (2.0) et des expressions lambda (3.0).
Objectif-CJavades classes internes anonymes .
AllerLimboNewsqueakRouillerLangages fonctionnelsZézayerSchèmeJuliaClojureMLHaskelljqScalaErlangÉlixirFa#OCamlLangages de scriptIoJavaScriptLuaPHPPerlPythonRubisAutres languesFortranÉrableMathematicaMATLABConversationRapideEn C++11, les fermetures peuvent capturer des variables non locales par copie, par référence (sans prolonger leur durée de vie) ou par déplacement (la variable est conservée tant que la fermeture est active). La première option est sûre si la fermeture est retournée, mais elle nécessite une copie et ne permet pas de modifier la variable d'origine (qui peut ne plus exister au moment de l'appel de la fermeture). La deuxième option permet d'éviter une copie coûteuse et autorise la modification de la variable d'origine, mais elle est dangereuse si la fermeture est retournée (voir les références orphelines ). La troisième option est sûre si la fermeture est retournée et ne nécessite pas de copie, mais ne permet pas non plus de modifier la variable d'origine.
Java
En Java 8, les fermetures ne peuvent capturer que des variables non locales finales ou « effectivement finales ». Les types de fonctions Java sont représentés par des classes. Les fonctions anonymes adoptent le type inféré du contexte. Les références de méthodes sont limitées. Pour plus de détails, consultez la section «Les variantes Lisp à portée lexicale prennent en charge les fermetures. Les variantes à portée dynamique ne prennent pas en charge les fermetures ou nécessitent une construction spéciale pour créer des fermetures.
En Common Lisp , l'identificateur d'une fonction dans l'espace de noms des fonctions ne peut pas être utilisé comme référence à une valeur de première classe. L'opérateur spécial `__init__` functiondoit être utilisé pour récupérer la fonction en tant que valeur : ` (function foo)__init__` renvoie un objet fonction. `__init__` #'fooexiste comme notation abrégée. Pour appliquer un tel objet fonction, il faut utiliser la funcallfonction `__init__` (funcall #'foo bar baz).
Python
Application partielle explicite functools.partialdepuis la version 2.5 et operator.methodcallerdepuis la version 2.6.
Rubis
L'identifiant d'une fonction classique en Ruby (qui est en réalité une méthode) ne peut être utilisé comme valeur ni passé en paramètre. Il doit d'abord être récupéré dans un objet `function` Methodou ` std Proc::function` pour pouvoir être utilisé comme donnée de premier ordre. La syntaxe d'appel d'un tel objet fonction diffère de celle utilisée pour appeler une méthode classique.
Les définitions de méthodes imbriquées n'imbriquent pas réellement la portée.
Curry explicite avec .

Plus d articles de Worldlex Wiki

Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

Explorer l index