Les fermetures sont-elles considérées comme impures dans la programmation fonctionnelle?
Il semble que l'on puisse généralement éviter les fermetures en transmettant des valeurs directement à une fonction. Par conséquent, les fermetures devraient-elles être évitées autant que possible?
S'ils sont impurs, et j'ai raison de dire qu'ils peuvent être évités, pourquoi tant de langages de programmation fonctionnels prennent-ils en charge les fermetures?
L'un des critères d'une fonction pure est le suivant: "La fonction évalue toujours la même valeur de résultat avec la même valeur d'argument ."
Supposer
f: x -> x + y
f(3)
ne donnera pas toujours le même résultat. f(3)
dépend de la valeur de y
ce qui n’est pas un argument de f
. Ainsi f
n'est pas une fonction pure.
Puisque toutes les fermetures reposent sur des valeurs qui ne sont pas des arguments, comment une fermeture peut-elle être pure? Oui, en théorie, la valeur fermée pourrait être constante, mais il n’ya aucun moyen de le savoir simplement en consultant le code source de la fonction elle-même.
Là où cela m'amène, c'est que la même fonction peut être pure dans une situation mais impure dans une autre. On ne peut pas toujours déterminer si une fonction est pure ou pas en étudiant son code source. Au lieu de cela, on peut être amené à la considérer dans le contexte de son environnement au moment où elle est appelée avant que cette distinction puisse être faite.
Est-ce que j'y pense correctement?
la source
y
ne peut pas changer, donc le résultat def(3)
sera toujours le même.y
fait partie de la définition def
même si elle n'est pas explicitement désignée comme une entrée dansf
- c'est toujours le cas quif
est défini en termes dey
(on pourrait désigner la fonction f_y, pour rendre la dépendance ày
explicitement), et donc changery
donne une fonction différente . La fonction particulièref_y
définie pour un particuliery
est très pure. (Par exemple, les deux fonctionsf: x -> x + 3
etf: x -> x + 5
sont des fonctions différentes , et toutes deux pures, même si nous avons utilisé la même lettre pour les désigner.)Réponses:
La pureté peut être mesurée par deux choses:
Si la réponse à 1 est oui et la réponse à 2 est non, alors la fonction est pure. Les fermetures ne rendent une fonction impure que si vous modifiez la variable fermée.
la source
y
. Ainsi, par exemple, nous définissons une fonctiong
telle qu'elleg(y)
est elle-même la fonctionx -> x + y
. Ensuite,g
une fonction d'entiers qui renvoie des fonctions,g(3)
une fonction d'entiers qui renvoie des entiers etg(2)
une fonction différente des entiers qui renvoie des entiers. Les trois fonctions sont pures.Les fermetures apparaissent Lambda Calculus, qui est la forme la plus pure de programmation fonctionnelle possible, alors je ne les appellerais pas "impures" ...
Les fermetures ne sont pas "impures" car les fonctions dans les langages fonctionnels sont des citoyens de première classe - cela signifie qu'elles peuvent être traitées comme des valeurs.
Imaginez ceci (pseudocode):
y
est une valeur. Sa valeur dépendx
, maisx
est immuabley
, sa valeur est également immuable. Nous pouvons appelerfoo
plusieurs fois avec des arguments différents qui produiront desy
s différents , mais cesy
s vivent tous dans des domaines différents et dépendent dex
s différents pour que la pureté reste intacte.Maintenant changeons-le:
Nous utilisons ici une fermeture (nous fermons sur x), mais c'est la même chose que in
foo
- des appelsbar
différents avec des arguments différents créent différentes valeurs dey
(souvenez-vous - les valeurs sont des valeurs) qui sont toutes immuables, de sorte que la pureté reste intacte.Veuillez également noter que les fermetures ont un effet très similaire au currying:
baz
n'est pas vraiment différent debar
- dans les deux cas, nous créons une valeur de fonction nomméey
qui retourne son argument plusx
. En fait, dans Lambda Calculus, vous utilisez des fermetures pour créer des fonctions avec plusieurs arguments - et ce n’est toujours pas impur.la source
D'autres ont très bien couvert la question générale dans leurs réponses, je ne ferai donc que regarder pour dissiper la confusion que vous signalez dans votre édition.
La fermeture ne devient pas une entrée de la fonction, elle "va" dans le corps de la fonction. Pour être plus concret, une fonction fait référence à une valeur dans l'étendue externe dans son corps.
Vous avez l'impression que cela rend la fonction impure. Ce n'est pas le cas, en général. En programmation fonctionnelle, les valeurs sont immuables la plupart du temps . Cela s'applique également à la valeur fermée.
Disons que vous avez un morceau de code comme celui-ci:
L' appel
make 3
etmake 4
vous donnera deux fonctions avec des fermetures plusmake
de »y
argument. L'un d'eux reviendrax + 3
, l'autrex + 4
. Ce sont deux fonctions distinctes cependant, et les deux sont purs. Ils ont été créés en utilisant la mêmemake
fonction, mais c'est tout.Notez le plus souvent quelques paragraphes en arrière.
Notez que pour 2 et 3, ces langues n'offrent aucune garantie de pureté. L'impureté n'y est pas une propriété de la fermeture, mais de la langue elle-même. Les fermetures ne changent pas beaucoup la situation par elles-mêmes.
la source
IO A
valeur immuable , et votre type de fermeture estIO (B -> C)
ou est similaire. La pureté est maintenueNormalement, je vous demanderais de clarifier votre définition de «impur», mais dans ce cas, cela n'a pas vraiment d'importance. En supposant que vous contrastiez avec le terme purement fonctionnel , la réponse est non, car rien dans les fermetures est intrinsèquement destructeur. Si votre langage était purement fonctionnel sans fermetures, il serait toujours purement fonctionnel avec des fermetures. Si au lieu de cela, vous voulez dire "non fonctionnel", la réponse est toujours "non"; les fermetures facilitent la création de fonctions.
Oui, mais votre fonction aurait alors un paramètre supplémentaire, ce qui changerait son type. Les fermetures vous permettent de créer des fonctions basées sur des variables sans ajouter de paramètres. Ceci est utile lorsque vous avez, par exemple, une fonction qui prend 2 arguments et que vous voulez en créer une version qui ne prend qu'un argument.
EDIT: En ce qui concerne votre propre édition / exemple ...
Dépend est le mauvais choix de mot ici. En citant le même article que vous avez fait sur Wikipedia:
En supposant que
y
soit immuable (ce qui est généralement le cas dans les langages fonctionnels), la condition 1 est remplie: pour toutes les valeurs dex
, la valeur def(x)
ne change pas. Cela devrait être clair du fait que cey
n'est pas différent d'une constante, etx + 3
est pur. Il est également clair qu'il n'y a pas de mutation ou d'E / S en cours.la source
Très rapidement: une substitution est "référentiellement transparente" si "la substitution de" conduit à ", et une fonction est" pure "si tous ses effets sont contenus dans sa valeur de retour. Les deux peuvent être précisés, mais il est essentiel de noter qu'ils ne sont pas identiques et que même l'un n'implique pas l'autre.
Parlons maintenant des fermetures.
"Fermetures" ennuyeuses (généralement pures)
Les fermetures se produisent parce que lorsque nous évaluons un terme lambda, nous interprétons les variables (liées) comme des recherches d’environnement. Ainsi, lorsque nous renvoyons un terme lambda à la suite d’une évaluation, les variables qu’il contient auront "fermé" sur les valeurs qu’ils avaient prises lors de sa définition.
Dans le lambda calcul, cela est trivial et toute la notion disparaît. Pour démontrer cela, voici un interprète de calcul lambda relativement léger:
La partie importante à noter est
addEnv
lorsque nous ajoutons un nouveau nom à l'environnement. Cette fonction est appelée uniquement "à l'intérieur" duAbs
terme de traction interprété (terme lambda). L’environnement est "recherché" chaque fois que nous évaluons unVar
terme, ce qui permet deVar
résoudre le problèmeName
mentionné dans leEnv
qui a été capturé par laAbs
traction contenant leVar
.Maintenant, encore une fois, en termes simples, LC est ennuyeux. Cela signifie que les variables liées ne sont que des constantes à la portée de tous. Elles sont évaluées directement et immédiatement en tant que valeurs désignées dans l'environnement comme ayant une portée lexicale jusque-là.
C'est aussi (presque) pur. La seule signification d'un terme dans notre lambda calcul est déterminée par sa valeur de retour. La seule exception est l’effet secondaire de la non-résiliation qui est matérialisé par le terme Omega:
Fermetures intéressantes (impures)
Maintenant, dans certains contextes, les fermetures décrites dans LC ci-dessus sont ennuyeuses car il n’ya aucune notion d’interaction avec les variables sur lesquelles nous avons fermé. En particulier, le mot "fermeture" a tendance à invoquer un code comme le code Javascript suivant
Cela montre que nous avons fermé la
n
variable dans la fonction interneincr
et que l'appelincr
interagit de manière significative avec cette variable.mk_counter
est pur, maisincr
est décidément impur (et non pas référentiellement transparent non plus).Qu'est-ce qui diffère entre ces deux instances?
Notions de "variable"
Si nous examinons la signification de substitution et d'abstraction au sens propre du terme LC, nous remarquons qu'elles sont clairement claires. Les variables ne sont littéralement rien de plus que des recherches immédiates sur l'environnement. L'abstraction Lambda n'est littéralement rien de plus que la création d'un environnement augmenté pour évaluer l'expression interne. Ce modèle ne laisse aucune place au type de comportement que nous avons observé avec
mk_counter
/incr
car aucune variation n’est autorisée.Pour beaucoup, c’est le cœur de ce que signifie "variable": la variation. Cependant, les sémanticistes aiment faire la distinction entre le type de variable utilisé en LC et le type de "variable" utilisé en Javascript. Pour ce faire, ils ont tendance à appeler ce dernier une "cellule mutable" ou un "slot".
Cette nomenclature suit le long usage historique de "variable" en mathématique, où il signifiait plutôt "inconnu": l'expression (mathématique) "x + x" ne permet pas
x
de varier dans le temps, elle a plutôt un sens de la valeur (unique, constante)x
prend.Ainsi, nous disons «slot» pour souligner la capacité de mettre des valeurs dans un slot et de les supprimer.
Pour ajouter à la confusion, en Javascript ces "slots" ressemblent à des variables: nous écrivons
pour en créer un, puis quand nous écrivons
cela nous indique que nous recherchons la valeur actuellement stockée dans cet emplacement. Pour rendre cela plus clair, les langues pures ont tendance à penser que les machines à sous prennent des noms comme des noms (mathématiques, lambda calcul). Dans ce cas, nous devons explicitement étiqueter quand nous obtenons ou mettons depuis un emplacement. Une telle notation a tendance à ressembler à
L'avantage de cette notation est que nous avons maintenant une distinction nette entre les variables mathématiques et les slots mutables. Les variables peuvent prendre des slots comme valeurs, mais le slot particulier nommé par une variable est constant sur toute sa portée.
En utilisant cette notation, nous pouvons réécrire l’
mk_counter
exemple (cette fois-ci dans une syntaxe semblable à Haskell, bien qu’une sémantique bien différente de celle de Haskell):Dans ce cas, nous utilisons des procédures qui manipulent cet emplacement mutable. Afin de l'implémenter, nous devrions fermer non seulement un environnement constant de noms,
x
mais également un environnement mutable contenant tous les emplacements nécessaires. Ceci est plus proche de la notion commune de "fermeture" que les gens aiment tellement.Encore une fois,
mkCounter
c'est très impur. C'est aussi très opaque par rapport au référentiel. Mais remarquez que les effets secondaires ne découlent pas de la capture du nom ou de la fermeture du nom, mais plutôt de la capture de la cellule mutable et des opérations produisant des effets secondaires sur elleget
etput
.En fin de compte, je pense que c'est la réponse finale à votre question: la pureté n'est pas affectée par la capture de variables (mathématiques), mais par les opérations à effets secondaires effectuées sur des emplacements mutables nommés par des variables capturées.
Ce n’est que dans les langues qui ne cherchent pas à être proches de LC ou à maintenir la pureté que ces deux concepts sont si souvent confondus, ce qui conduit à la confusion.
la source
Non, les fermetures ne rendent pas une fonction impure tant que la valeur fermée est constante (ni modifiée par la fermeture ni par un autre code), ce qui est le cas habituel en programmation fonctionnelle.
Notez que si vous pouvez toujours passer une valeur sous forme d'argument, vous ne pouvez généralement pas le faire sans beaucoup de difficulté. Par exemple (coffeescript):
Sur votre suggestion, vous pouvez simplement revenir:
Cette fonction n'est pas appelée à ce stade, elle est simplement définie . Vous devez donc trouver un moyen de transmettre la valeur souhaitée
closedValue
au point où la fonction est appelée. Au mieux, cela crée beaucoup de couplage. Dans le pire des cas, vous ne contrôlez pas le code au niveau du point d’appel, c’est donc impossible.Les bibliothèques d'événements dans des langages qui ne prennent pas en charge les fermetures offrent généralement un autre moyen de transmettre des données arbitraires au rappel, mais ce n'est pas beau et crée beaucoup de complexité à la fois pour le responsable de la bibliothèque et pour les utilisateurs.
la source