Une fonction est-elle immédiatement impure si elle prend une fonction en paramètre?

17

Étant donné que la pureté d'un paramètre d'entrée est inconnue jusqu'à l'exécution, une fonction est-elle immédiatement considérée comme impure si elle prend une fonction comme paramètre d'entrée?

Connexes: si une fonction applique une fonction pure qui est définie en dehors de la fonction, mais qui n'est pas transmise en tant que paramètre, est-elle toujours pure si elle remplit les critères de l'absence d'effets secondaires et la sortie dépend uniquement de l'entrée?

Pour le contexte, j'écris du code fonctionnel en JavaScript.

Dancrumb
la source
Comme contre-exemple trivial, considérons:foo = function(function bar){ print(bar.toString()) }
David dit Réintégrer Monica
1
@DavidGrinberg Ce n'est pas un contre-exemple, je pense, et met en évidence un problème plus important; si vous avez des fonctions qui peuvent être remplacées et que vous ne pouvez pas garantir que les implémentations sont sans effet secondaire, vous ne pouvez pas garantir que la plupart des fonctions qui prennent des objets et appellent leurs méthodes sont pures non plus. Peut-être que la barre toString () supprime certains fichiers du disque?
Joshua Taylor
3
@DavidGrinberg Mais, je pense que vous pensez dans la bonne direction. foo = function(function bar) { return 3; } est pur et prend une fonction en argument.
Joshua Taylor
@JoshuaTaylor Fair point, je n'y ai pas pensé. Mais vous avez déjà essentiellement résolu le problème. Comme solution alternative, appelez simplement la «racine» toString()(c'est-à-dire celle que vous trouveriez sur l'objet Java).
David dit de rétablir Monica

Réponses:

22

Tant que toutes les valeurs utilisées dans la fonction sont définies uniquement par ses paramètres, c'est une fonction pure.

La facette que la sortie est la même à chaque fois pour la même entrée est contrôlée par la pureté des paramètres. Si vous supposez que les paramètres (comme un argument de fonction) sont également purs, alors c'est pur.

Dans un langage comme Javascript où la pureté n'est pas appliquée, cela signifie qu'il est possible de faire en sorte qu'une fonction par ailleurs pure ait un comportement impur en invoquant une fonction impure passée en paramètre.

Cela signifie effectivement que pour les langages qui n'imposent pas la pureté (c'est-à-dire presque tous), il est impossible de définir une fonction pure qui invoque des fonctions passées en arguments. Il est toujours utile de les écrire aussi pur que possible et de les raisonner en tant que fonctions pures, mais vous devez faire preuve de prudence car l'hypothèse selon laquelle elle est pure sera brisée si vous passez les mauvais arguments.

D'après mon expérience dans la pratique, ce n'est généralement pas un gros problème - je trouve rare que des fonctions impures soient utilisées comme arguments de fonction pour des fonctions pures.

Daenyth
la source
Concernant votre affirmation selon laquelle "tant que toutes les valeurs utilisées dans la fonction sont définies uniquement par ses paramètres, c'est une fonction pure". Que se passe-t-il dans le cas des constantes? Si j'ai une fonction areaOfCircle r => Math.Pi * r * r, sera areaOfCircle-t-elle non pure car elle n'utilisera pas seulement des paramètres?
David Arno
2
@DavidArno C'est un bon point. Selon la transparence référentielle, se référer à une valeur extérieure statique ne serait pas différent de l'avoir codée en dur, elle serait donc toujours pure.
Daenyth
1
"cela signifie qu'il est possible de faire en sorte qu'une fonction pure ait un comportement impur" - par définition, une fonction pure ne peut pas avoir un comportement impur. Vous faites l'erreur de penser qu'une fonction f(f2)qui invoque f2ne dépend pas de façon transitoire de quoi que ce soit f2. Une fonction qui pourrait appeler des fonctions transmises arbitrairement n'est pas pure.
user2357112 prend en charge Monica
2
@Daenyth: Mieux, mais cela suppose toujours que la fonction doit appeler la fonction transmise. Cela pourrait être quelque chose comme function compose(f, g) {return function h(x) {return f(g(x));};}, ce qui est pur malgré la prise de fonctions comme arguments.
user2357112 prend en charge Monica
1
"Je trouve rare que des fonctions impures soient utilisées comme arguments de fonction pour des fonctions pures." - pas un langage fonctionnel, mais certaines fonctions de bibliothèque C ++ ont des avertissements spécifiques que les arguments de prédicat doivent être (une approximation à) purs. Donc, dans un sens, cela signifie que ce n'est pas seulement rare, cela ne se produit jamais valablement. Mais dans un autre sens, le fait qu'ils doivent l'interdire est que les gens veulent parfois le faire. Par exemple, ils veulent transmettre à un sous- findprogramme un prédicat impur qui renvoie "vrai" pour le troisième élément correspondant qu'il rencontre, ou un certain non-sens.
Steve Jessop
19

Étant donné que la pureté d'un paramètre d'entrée est inconnue jusqu'à l'exécution, une fonction est-elle immédiatement considérée comme impure si elle prend une fonction comme paramètre d'entrée?

Non. Contre-exemple:

function pure(other_function) {
    return 1;
}

Peu importe qu'il s'agisse d' other_functionune fonction pure, d'une fonction impure ou pas du tout. La purefonction est pure.

Autre contre-exemple:

function identity(x) {
    return x;
}

Cette fonction est pure, même si elle xest impure. identity(impure_function)sera toujours de retour impure_function, peu importe combien de fois vous répétez l'appel. Peu importe que cela identity(impure_function)()renvoie toujours la même chose; La valeur de retour d'une valeur de retour d' une fonction n'affecte pas sa pureté.


En général, si une fonction peut appeler une fonction, elle a été passée en argument, elle n'est pas pure. Par exemple, une fonction function call(f) {f();}n'est pas pure, car même si elle ne fait mention d'aucun état global ou mutable, cela fpourrait alertprovoquer des effets secondaires visibles.

Si une fonction prend des fonctions comme arguments, mais qu'elle ne les appelle pas ou ne les fait pas appeler, elle peut être pure. Il pourrait encore être impur s'il fait une autre chose impure. Par exemple, function f(ignored_function) {alert('This isn't pure.');}est impur, même s'il n'appelle jamais ignored_function.

user2357112 prend en charge Monica
la source
4
Cette réponse semble trop pédante. Nous pouvons déduire de la question que la préoccupation concerne le paramètre de fonction invoqué. L'existence de fonctions qui peuvent / prennent d'autres fonctions en paramètre sans les invoquer n'affecte pas cette question.
walpen
13
@walpen: La question ne fait aucune mention de l'invocation de l'argument. Il n'y a aucune raison de supposer que le questionneur a même réalisé qu'une fonction pouvait prendre une autre fonction en entrée sans l'invoquer. Il est important de souligner des hypothèses cachées comme celle-ci, plutôt que de simplement supposer que vous étiez censé les assumer.
user2357112 prend en charge Monica
12

Étant donné que la pureté d'un paramètre d'entrée est inconnue jusqu'à l'exécution, une fonction est-elle immédiatement considérée comme impure si elle prend une fonction comme paramètre d'entrée?

Techniquement, oui, sauf s'il existe un moyen dans votre langue de garantir que la fonction d'entrée est également pure.

si une fonction applique une fonction pure qui est définie en dehors de la fonction, mais qui n'est pas transmise en tant que paramètre, est-elle toujours pure si elle remplit les critères de l'absence d'effets secondaires et la sortie dépend uniquement de l'entrée?

Oui. Concentrons-nous donc sur ce qui compte ici. Appeler une fonction pure ou non n'est pas en soi utile. Les fonctions pures sont utiles car produire la même sortie pour n'importe quelle entrée et ne pas dépendre de l'état ou avoir des effets secondaires est un ensemble de propriétés très utile. Cela signifie qu'une fois que votre fonction a été exécutée, vous pouvez vous «souvenir» de la réponse pour cette entrée et ce sera toujours vrai. Vous n'avez pas non plus besoin d'exécuter à nouveau la fonction pour générer des effets secondaires. Et vous pouvez exécuter cette fonction en parallèle (ou dans le désordre) avec d'autres fonctions et savoir qu'elles n'auront pas d'interactions cachées qui se comportent mal.

Ces propriétés utiles restent valables si la fonction utilise d'autres fonctions en lecture seule pour effectuer son travail, quelle que soit la façon dont elle les référence.

Telastyn
la source
5

Comme Telastyn l'a dit: Techniquement, oui, à moins qu'il n'y ait un moyen dans votre langue de garantir que la fonction d'entrée est également pure.

Ce n'est pas hypothétique, il existe en effet de bons moyens de garantir cela. Au moins dans une langue fortement typée.

Une telle fonction ~ pure que vous écririez en JavaScript

function foo(f) {
   return f(1) + 2;
}

peut être traduit directement en Haskell:

foo :: (Int -> Int) -> Int
foo f = f 1 + 2

Maintenant, en JavaScript, vous pouvez faire des trucs diaboliques comme

js> foo (function(x) {console.log("muharhar"); return 0})
muharhar
2

Ce n'est pas possible à Haskell . La raison en est que quelque chose d'effet secondaire console.log()doit toujours avoir un type de résultat IO something, pas seulement somethingseul.

GHCi> foo (\x -> print "muarhar" >> return 0)

<interactive>:7:12:
    Couldn't match expected type ‘Int’ with actual type ‘IO b0’
    In the expression: print "muarhar" >> return 0
    In the first argument of ‘foo’, namely
      ‘(\ x -> print "muarhar" >> return 0)’
    In the expression: foo (\ x -> print "muarhar" >> return 0)

Pour que cette expression soit vérifiée, nous aurions besoin de donner foola signature de type

foo :: (Int -> IO Int) -> Int

Mais il s'avère que je ne peux plus l'implémenter alors: parce que la fonction d'argument a IOdans son résultat, je ne peux pas l'utiliser à l'intérieur foo.

<interactive>:8:44:
    Couldn't match expected type ‘Int’ with actual type ‘IO Int’
    In the first argument of ‘(+)’, namely ‘f 1’
    In the expression: f 1 + 2

La seule façon dont je pourrais utiliser une IOaction fooest si le résultat de fooa IO Intlui-même le type :

foo :: (Int -> IO Int) -> IO Int
foo f = do
   f1 <- f 1
   return (f1 + 2)

Mais à ce stade, il ressort clairement de la signature fooque ce n'est pas une fonction pure non plus.

à gauche
la source
1
Avant de dire "impossible", jetez un œil à unsafeIO:-)
Bergi
2
@Bergi: cela ne fait en fait pas partie de Haskell, mais de son interface de fonction étrangère: pour permettre d'affirmer qu'une fonction définie dans un autre langage est pure, ce que le compilateur Haskell ne peut évidemment pas déduire de la signature de type car les autres langages n'ont généralement pas une telle chose que IO. Soit dit en passant, il peut également être utilisé pour provoquer le chaos en masquant les effets secondaires dans une fonction «pure», mais ce n'est vraiment pas sûr dans Haskell car il n'y a pas vraiment de moyen fiable de spécifier l'ordre d'évaluation des fonctions pures.
leftaroundabout
Ouais c'est vrai. Cependant, je pense que je l'ai vu être utilisé pour "cacher" des effets secondaires bénéfiques dans une fonction "pure" également, où la sécurité de l'approche devait être établie auparavant.
Bergi
@Bergi Vous ne devriez surtout pas utiliser unsafeIO; c'est une trappe d'évacuation de dernier recours qui contourne les garanties du système de type, et donc la vôtre n'est pas un bon point.
Andres F.
0

Non ça ne l'est pas.

Si la fonction passée est impure ET que votre fonction appelle la fonction passée, alors votre fonction sera considérée comme impure.

La relation pure / impure est un peu comme sync / async dans JS. Vous pouvez librement utiliser du code pur impur, mais pas l'inverse.

Bobby Marinoff
la source
Cette réponse n'ajoute rien qui ne soit déjà expliqué dans celle-ci ... Veuillez revoir les réponses précédentes avant de reformuler les choses :)
Andres F.
Qu'en est-il de l'analogie sync / async?
Bobby Marinoff