Les fermetures avec effets secondaires sont-elles considérées comme un «style fonctionnel»?

9

De nombreux langages de programmation modernes prennent en charge un certain concept de fermeture , c'est-à-dire d'un morceau de code (un bloc ou une fonction) qui

  1. Peut être traité comme une valeur, et donc stocké dans une variable, transmis à différentes parties du code, être défini dans une partie d'un programme et appelé dans une partie totalement différente du même programme.
  2. Peut capturer des variables à partir du contexte dans lequel il est défini et y accéder lors de son invocation ultérieure (éventuellement dans un contexte totalement différent).

Voici un exemple de fermeture écrit en Scala:

def filterList(xs: List[Int], lowerBound: Int): List[Int] =
  xs.filter(x => x >= lowerBound)

Le littéral de fonction x => x >= lowerBoundcontient la variable libre lowerBound, qui est fermée (liée) par l'argument de la fonction filterListqui porte le même nom. La fermeture est transmise à la méthode de bibliothèque filter, qui peut l'invoquer à plusieurs reprises en tant que fonction normale.

J'ai lu beaucoup de questions et réponses sur ce site et, si je comprends bien, le terme fermeture est souvent automatiquement associé à la programmation fonctionnelle et au style de programmation fonctionnelle.

La définition de la programmation des fonctions sur wikipedia se lit comme suit:

En informatique, la programmation fonctionnelle est un paradigme de programmation qui traite le calcul comme l'évaluation de fonctions mathématiques et évite les données d'état et mutables. Il met l'accent sur l'application des fonctions, contrairement au style de programmation impératif, qui met l'accent sur les changements d'état.

et plus loin

[...] dans le code fonctionnel, la valeur de sortie d'une fonction dépend uniquement des arguments qui sont entrés dans la fonction [...]. L'élimination des effets secondaires peut faciliter la compréhension et la prévision du comportement d'un programme, ce qui est l'une des principales motivations pour le développement d'une programmation fonctionnelle.

D'un autre côté, de nombreuses constructions de fermeture fournies par les langages de programmation permettent à une fermeture de capturer des variables non locales et de les modifier lorsque la fermeture est invoquée, produisant ainsi un effet secondaire sur l'environnement dans lequel elles ont été définies.

Dans ce cas, les fermetures implémentent la première idée de programmation fonctionnelle (les fonctions sont des entités de première classe qui peuvent être déplacées comme les autres valeurs) mais négligent la deuxième idée (en évitant les effets secondaires).

Cette utilisation des fermetures avec effets secondaires est-elle considérée comme un style fonctionnel ou les fermetures sont-elles considérées comme une construction plus générale qui peut être utilisée à la fois pour un style de programmation fonctionnel et non fonctionnel? Existe-t-il de la littérature sur ce sujet?

NOTE IMPORTANTE

Je ne remets pas en question l'utilité des effets secondaires ou des fermetures avec effets secondaires. De plus, je ne suis pas intéressé par une discussion sur les avantages / inconvénients des fermetures avec ou sans effets secondaires.

Je suis seulement intéressé de savoir si l'utilisation de telles fermetures est toujours considérée comme un style fonctionnel par le promoteur de la programmation fonctionnelle ou si, au contraire, leur utilisation est déconseillée lors de l'utilisation d'un style fonctionnel.

Giorgio
la source
3
Dans un style / langage purement fonctionnel, les effets secondaires sont impossibles ... alors je suppose que ce serait une question de: à quel niveau de pureté considère-t-on le code comme fonctionnel?
Steven Evers
@SnOrfus: à 100%?
Giorgio
1
Les effets secondaires sont impossibles dans un langage purement fonctionnel, ce qui devrait répondre à votre question.
Steven Evers
@SnOrfus: Dans de nombreux langages, les fermetures sont considérées comme une construction de programmation fonctionnelle, donc j'étais un peu confus. Il me semble que (1) leur utilisation est plus générale que simplement faire de la FP, et (2) l'utilisation de fermetures ne garantit pas automatiquement que l'on utilise un style fonctionnel.
Giorgio
Le downvoter peut-il laisser une note sur la façon d'améliorer cette question?
Giorgio

Réponses:

7

Non; la définition du paradigme fonctionnel concerne le manque d'état et implicitement le manque d'effets secondaires. Il ne s'agit pas de fonctions de haut niveau, de fermetures, de manipulation de liste prise en charge par la langue ou d'autres fonctionnalités de langage ...

Le nom de la programmation fonctionnelle vient de la notion mathématique de fonctions - des appels répétés sur la même entrée donnent toujours la même sortie - fonction nullipotente. Cela ne peut être réalisé que si les données sont immuables . Afin de faciliter le développement, les fonctions sont devenues mutables (les fonctions changent, les données sont toujours immuables) et donc la notion de fonctions d'ordre supérieur (fonctionnelles en mathématiques, comme dérivées par exemple) - une fonction qui prend en entrée une autre fonction. Pour la possibilité de transporter des fonctions et de les passer comme arguments, des fonctions de première classe ont été adoptées; après cela, pour améliorer encore la productivité, des fermetures sont apparues.

C'est, bien sûr, une vue très simplifiée.

m3th0dman
la source
3
Les fonctions d'ordre supérieur n'ont absolument rien à voir avec la mutabilité, pas plus que les fonctions de première classe. Je peux faire circuler des fonctions et en construire d'autres fonctions dans Haskell avec une grande facilité, sans avoir d'état mutable ni d'effets secondaires.
tdammers
@tdammers, je n'ai probablement pas exprimé très clairement; quand j'ai dit que les fonctions sont devenues mutables, je n'ai pas fait référence à la mutabilité des données mais au fait que le comportement d'une fonction peut être changé (par une fonction de haut niveau).
m3th0dman
Une fonction d'ordre supérieur n'a pas à modifier une fonction existante; la plupart des exemples de manuels combinent des fonctions existantes dans de nouvelles fonctions sans les modifier du tout - prenez map, par exemple, qui prend une fonction, l'applique à une liste et renvoie une liste des résultats. mapne modifie aucun de ses arguments, il ne change pas le comportement de la fonction qu'il prend comme argument, mais c'est définitivement une fonction d'ordre supérieur - si vous l'appliquez partiellement, avec juste le paramètre de fonction, vous avez construit un nouvelle fonction qui fonctionne sur une liste, mais toujours aucune mutation n'est survenue.
tdammers
@tdammers: Exactement: la mutabilité n'est utilisée que dans les langages impératifs ou multi-paradigmes. Même si celles-ci peuvent avoir le concept d'une fonction (ou méthode) d'ordre supérieur et d'une fermeture, elles renoncent à l'immuabilité. Cela peut être utile (je ne dis pas qu'il ne faut pas le faire) mais ma question est de savoir si vous pouvez toujours appeler cette fonction. Ce qui signifierait qu'en général, une fermeture n'est pas un concept strictement fonctionnel.
Giorgio
@Giorgio: la plupart des langages de PF classiques n'ont mutabilité; Haskell est la seule à laquelle je peux penser du haut de ma tête qui ne permette pas du tout la mutabilité. Pourtant, éviter l' état mutable est une valeur importante en PF.
tdammers
9

Non. Le "style fonctionnel" implique une programmation sans effet secondaire.

Pour voir pourquoi, jetez un œil à l'entrée de blog d'Eric Lippert sur la ForEach<T>méthode d'extension, et pourquoi Microsoft n'a pas inclus une méthode de séquence comme celle-ci dans Linq :

Je suis philosophiquement opposé à la fourniture d'une telle méthode, pour deux raisons.

La première raison est que cela viole les principes de programmation fonctionnelle sur lesquels tous les autres opérateurs de séquence sont basés. De toute évidence, le seul but d'un appel à cette méthode est de provoquer des effets secondaires. Le but d'une expression est de calculer une valeur, et non de provoquer un effet secondaire. Le but d'une déclaration est de provoquer un effet secondaire. Le site d'appel de cette chose ressemblerait énormément à une expression (bien que, certes, puisque la méthode renvoie un vide, l'expression ne puisse être utilisée que dans un contexte d '«expression expression».) Cela ne me convient pas faire le seul et unique opérateur de séquence qui n'est utile que pour ses effets secondaires.

La deuxième raison est que cela n'ajoute aucun nouveau pouvoir de représentation au langage. Cela vous permet de réécrire ce code parfaitement clair:

foreach(Foo foo in foos){ statement involving foo; }

dans ce code:

foos.ForEach((Foo foo)=>{ statement involving foo; });

qui utilise presque exactement les mêmes caractères dans un ordre légèrement différent. Et pourtant, la deuxième version est plus difficile à comprendre, plus difficile à déboguer, et introduit une sémantique de fermeture, modifiant ainsi potentiellement la durée de vie des objets de manière subtile.

Robert Harvey
la source
1
Cependant, je suis d'accord avec lui ... son argument s'affaiblit un peu quand on le considère ParallelQuery<T>.ForAll(...). L'implémentation d'un tel IEnumerable<T>.ForEach(...)est extrêmement utile pour le débogage des ForAllinstructions (remplacez le ForAllpar ForEachet supprimez le AsParallel()et vous pouvez beaucoup plus facilement le parcourir / le déboguer)
Steven Evers
Clairement, Joe Duffy a des idées différentes. : D
Robert Harvey
Scala n'applique pas non plus la pureté: vous pouvez passer une fermeture non pure à une fonction d'ordre élevé. Mon impression est que l'idée d'une fermeture n'est pas spécifique à la programmation fonctionnelle mais c'est une idée plus générale.
Giorgio
5
@Giorgio: Les fermetures n'ont pas besoin d'être pures pour être considérées comme des fermetures. Cependant, ils doivent être purs pour être considérés comme un «style fonctionnel».
Robert Harvey
3
@Giorgio: Il est vraiment utile de pouvoir clore l'état mutable et les effets secondaires et de le passer à une autre fonction. C'est tout simplement contraire aux objectifs de la programmation fonctionnelle. Je pense que beaucoup de confusion vient du support de langage élégant pour les lambdas étant courants dans les langages fonctionnels.
GlenPeterson
0

La programmation fonctionnelle fait certes passer les fonctions de première classe au niveau conceptuel suivant, mais déclarer des fonctions anonymes ou passer des fonctions à d'autres fonctions n'est pas nécessairement une chose de programmation fonctionnelle. En C, tout était un entier. Un nombre, un pointeur sur les données, un pointeur sur une fonction ... tout simplement des entiers. Vous pouvez passer des pointeurs de fonction à d'autres fonctions, faire des listes de pointeurs de fonction ... Heck, si vous travaillez en langage assembleur, les fonctions ne sont vraiment que des adresses en mémoire où sont stockés des blocs d'instructions machine. Donner un nom à une fonction est une surcharge supplémentaire pour les personnes qui ont besoin d'un compilateur pour écrire du code. Les fonctions étaient donc «de première classe» dans ce sens dans un langage complètement non fonctionnel.

Si la seule chose que vous faites est de calculer des formules mathématiques dans un REPL, alors vous pouvez être fonctionnellement pur avec votre langage. Mais la plupart des programmes d'entreprise ont des effets secondaires. Perdre de l'argent en attendant la fin d'un programme de longue durée est un effet secondaire. Toute action externe: écrire dans un fichier, mettre à jour une base de données, consigner les événements dans l'ordre, etc. nécessite un changement d'état. Nous pourrions débattre de la question de savoir si l'état est vraiment changé si vous encapsulez ces actions dans des wrappers immuables qui repoussent les effets secondaires afin que votre code n'ait pas à s'en soucier. Mais c'est comme discuter si un arbre fait du bruit s'il tombe dans la forêt sans que personne ne l'entende. Le fait est que l'arbre a commencé en position verticale et s'est retrouvé au sol. L'état est changé lorsque les choses sont faites, ne serait-ce que pour signaler que les choses ont été faites.

Il nous reste donc une échelle de pureté fonctionnelle, pas du noir et du blanc, mais des nuances de gris. Et à cette échelle, moins il y a d'effets secondaires, moins il y a de mutabilité, mieux c'est (plus fonctionnel).

Si vous avez absolument besoin d'un effet secondaire ou d'un état mutable dans votre code par ailleurs fonctionnel, vous vous efforcez de l'encapsuler ou de le faire du reste de votre programme du mieux que vous le pouvez. L'utilisation d'une fermeture (ou de toute autre chose) pour injecter des effets secondaires ou un état mutable dans des fonctions par ailleurs pures est l'antithèse de la programmation fonctionnelle. La seule exception pourrait être si la fermeture était le moyen le plus efficace pour encapsuler les effets secondaires du code auquel elle est transmise. Ce n'est toujours pas de la «programmation fonctionnelle», mais c'est peut-être le placard que vous pouvez obtenir dans certaines situations.

GlenPeterson
la source