Comment appelez-vous une fonction où la même entrée renvoie toujours la même sortie, mais a également des effets secondaires?

43

Disons que nous avons une fonction pure normale telle que

function add(a, b) {
  return a + b
}

Et puis nous le modifions pour qu'il ait un effet secondaire

function add(a, b) {
  writeToDatabase(Math.random())
  return a + b;
}

Autant que je sache, ce n’est pas considéré comme une fonction pure, car j’entends souvent les gens appeler des fonctions pures "des fonctions sans effets secondaires". Cependant, il se comporte comme une fonction pure dans la mesure où il renvoie la même sortie pour les mêmes entrées.

Existe-t-il un nom différent pour ce type de fonction, est-il anonyme, ou est-il toujours pur et je me trompe au sujet de la définition de la pureté?

M0meni
la source
91
"Pas une fonction pure".
Ross Patterson
2
@RossPatterson, c'est ce que je pensais aussi, mais en posant la question, j'ai appris la transparence référentielle et je suis heureux de ne pas l'avoir gardée pour moi.
m0meni
9
En cas d' writeToDatabaseéchec, il pourrait déclencher une exception et faire en sorte que votre deuxième addfonction génère parfois une exception, même si elle est appelée avec les mêmes arguments qu'avant, cela ne posait pas de problème auparavant. "pureté entrée-sortie".
Bakuriu
25
Quelque chose qui donne toujours la même sortie pour une entrée donnée s'appelle déterministe .
njzk2
2
@ njzk2: C'est vrai, mais c'est aussi sans état . Une fonction déterministe avec état peut ne pas donner le même résultat pour chaque entrée. Exemple: F(x)est défini pour retourner trues'il est appelé avec le même argument que l'appel précédent. Clairement, avec la séquence, {1,2,2} => {undefined, false, true}c'est déterministe, mais cela donne différents résultats F(2).
MSalters

Réponses:

85

Je ne suis pas sûr des définitions universelles de la pureté, mais du point de vue de Haskell (un langage dans lequel les programmeurs ont tendance à se préoccuper de choses telles que la pureté et la transparence référentielle), seule la première de vos fonctions est "pure". La deuxième version de addn'est pas pure . Donc, en réponse à votre question, je l'appellerais "impur";)

Selon cette définition, une fonction pure est une fonction qui:

  1. Ne dépend que de son entrée. C'est-à-dire que, avec la même entrée, il retournera toujours la même sortie.
  2. Est référentiellement transparent: la fonction peut être remplacée librement par sa valeur et le "comportement" du programme ne changera pas.

Avec cette définition, il est clair que votre deuxième fonction ne peut pas être considérée comme pure, car elle enfreint la règle 2. Autrement dit, les deux programmes suivants ne sont PAS équivalents:

function f(a, b) { 
    return add(a, b) + add(a, b);
}

et

function g(a, b) {
    c = add(a, b);
    return c + c;
}

En effet, même si les deux fonctions renverront la même valeur, la fonction fécrira deux fois dans la base de données, mais gune fois! Il est très probable que les écritures dans la base de données fassent partie du comportement observable de votre programme. Dans ce cas, j'ai montré que votre deuxième version de addn'est pas "pure".

Si les écritures dans la base de données ne font pas partie du comportement de votre programme, les deux versions de addpeuvent être considérées comme équivalentes et pures. Mais je ne peux pas penser à un scénario où écrire dans la base de données importe peu. Même l'exploitation forestière compte!

Andres F.
la source
1
"Ne dépend que de son entrée" n'est-il pas redondant compte tenu de la transparence référentielle? Ce qui impliquerait que RT est synonyme de pureté? (Je suis de plus en plus confus à ce sujet, plus je cherche de sources)
Ixrec
Je suis d'accord, c'est déroutant. Je ne peux que penser à des exemples artificiels. Say f(x)dépend non seulement de x, mais aussi de certaines variables globales externes y. Ensuite, si fa la propriété de RT, vous pouvez échanger librement toutes ses occurrences avec sa valeur de retour tant que vous ne touchez pas y . Oui, mon exemple est douteux. Mais l’important est le suivant: si l’ fécriture dans la base de données (ou l’écriture dans un journal) perd la propriété de RT: peu importe que vous laissiez globalement yintact, vous savez que la signification de votre programme change en fonction de votre situation réelle. appelez fou utilisez simplement sa valeur de retour.
Andres F.
Humph. Disons que nous avons une telle fonction qui est pure, à l'exception des effets secondaires, et dont le comportement est également garanti lorsque vos deux échantillons sont équivalents. (J'ai eu ce cas en fait donc ce n'est pas hypothétique.) Je pense que nous n'avons pas tout à fait fini.
Joshua
2
Je dirais que la deuxième fonction pourrait aussi enfreindre la règle n ° 1. Selon la langue et les pratiques de traitement des erreurs de l'API de base de données utilisée, la fonction peut ne rien renvoyer du tout si la base de données n'est pas disponible ou si l'écriture de la base de données échoue pour une raison quelconque.
Zach Lipton
1
Depuis que Haskell a été mentionné: Dans Haskell, ajouter un effet secondaire comme celui-ci nécessite de modifier la signature de la fonction . (Pensez-y comme donnant la base de données d'origine comme une entrée supplémentaire et obtenant la base de données modifiée comme une valeur de retour supplémentaire de la fonction). Il est en fait possible de modéliser les effets secondaires de manière assez élégante dans le système de caractères de cette façon, c'est simplement que les langages traditionnels d'aujourd'hui ne se soucient pas suffisamment des effets secondaires et de la pureté pour le faire.
ComicSansMS
19

Comment appelez-vous une fonction [pour laquelle] la même entrée retournera toujours la même sortie, mais aura également des effets secondaires?

Une telle fonction s'appelle

déterministe

Un algorithme dont le comportement peut être complètement prédit à partir de l'entrée.

termwiki.com

En ce qui concerne l'état:

Selon la définition de la fonction que vous utilisez, une fonction n'a pas d'état. Si vous venez du monde orienté objet, rappelez-vous que x.f(y)c'est une méthode. En fonction, cela ressemblerait f(x,y). Et si vous êtes dans des fermetures avec une portée lexicale incluse, rappelez-vous que cet état immuable pourrait aussi bien faire partie de l'expression des fonctions. Ce n'est que l'état mutable qui aurait un impact sur la nature déterministe des fonctions. Donc, f (x) = x + 1 est déterministe tant que le 1 ne change pas. Peu importe où le 1 est stocké.

Vos fonctions sont déterministes. Votre première est également une fonction pure. Votre seconde n'est pas pure.

Fonction pure

  1. La fonction évalue toujours la même valeur de résultat avec les mêmes valeurs d'argument. La valeur du résultat de la fonction ne peut dépendre d'aucune information cachée ni d'aucun état susceptible de changer pendant l'exécution du programme ou entre différentes exécutions du programme, pas plus que de toute entrée externe provenant de périphériques d'E / S.

  2. L'évaluation du résultat ne provoque pas d'effet secondaire ou de sortie observable sémantiquement, tel que la mutation d'objets mutables ou la sortie sur des périphériques d'E / S.

wikipedia.org

Le point 1 signifie déterministe . Le point 2 signifie transparence référentielle . Ensemble, ils signifient qu'une fonction pure ne permet que ses arguments et sa valeur renvoyée à changer. Rien d'autre ne provoque le changement. Rien d'autre n'est changé.

confits_orange
la source
-1. L'écriture dans la base de données dépend d'un état externe qu'il est généralement impossible de déterminer en consultant les entrées. La base de données peut être indisponible pour un certain nombre de raisons et le succès de l'opération n'est pas prévisible. Ce n'est pas un comportement déterministe.
Frax
@Frax La mémoire système peut ne pas être disponible. Le processeur peut être indisponible. Être déterministe ne garantit pas le succès. Cela garantit qu'un comportement réussi est prévisible.
candied_orange
OOMing n'est spécifique à aucune fonction, il s'agit d'une catégorie de problèmes différente. Examinons maintenant le point 1 de votre définition de "fonction pure" (qui signifie en effet "déterministe"): "La valeur du résultat de la fonction ne peut dépendre d'aucune information ou état masqué susceptible de changer pendant l'exécution du programme ou entre différentes exécutions de programme , il ne peut pas non plus dépendre d’une entrée externe de périphériques d’E / S ". La base de données correspond à cet état, si bien que la fonction OP ne remplit manifestement pas cette condition - elle n'est pas déterministe.
Frax
@candied_orange Je serais d'accord si l'écriture dans la base de données ne dépendait que des entrées. Mais c'est Math.random(). Donc non, sauf si nous supposons un PRNG (au lieu d'un RNG physique) ET considérons que les PRNG font partie de l'entrée (ce qui n'est pas le cas, la référence est codée en dur), ce n'est pas déterministe.
Marstato
1
@candied_orange votre citation d'états déterministes "un algorithme dont le comportement peut être complètement prédit à partir de l'entrée." Ecrire pour IO est pour moi un comportement, pas un résultat.
Marstato
9

Si vous ne vous souciez pas de l'effet secondaire, il est transparent par référentiel. Bien sûr, il est possible que vous ne vous en souciiez pas, mais que quelqu'un d'autre le fasse. L'applicabilité du terme dépend donc du contexte.

Je ne connais pas de terme général pour désigner précisément les propriétés que vous décrivez, mais un sous-ensemble important est celui qui est idempotent . En informatique, un peu différemment en mathématiques *, une fonction idempotente est une fonction qui peut être répétée avec le même effet; c’est-à-dire que l’effet net de le faire plusieurs fois est le même que de le faire une fois.

Donc, si votre effet secondaire était de mettre à jour une base de données avec une certaine valeur dans une certaine ligne, ou de créer un fichier avec un contenu parfaitement cohérent, alors ce serait idempotent , mais s'il était ajouté à la base de données ou ajouté à un fichier alors ce ne serait pas.

Les combinaisons de fonctions idempotentes peuvent être ou non idempotentes dans leur ensemble.

* En informatique, l'utilisation d' idempotent différemment des mathématiques semble provenir d'une utilisation incorrecte du terme mathématique qui a ensuite été adopté parce que le concept est utile.

Jon Hanna
la source
3
le terme "référentiellement transparent" est défini plus strictement que "quiconque s'en soucie" ou non. même si l' on fait abstraction des problèmes IO tels que des problèmes de connexion, les chaînes de connexion manquantes, les délais d' attente, etc., il est toujours facile de montrer qu'un programme qui remplace (f x, f x)avec let y = f x in (y, y)se déroulera en dehors de l' espace disque-exceptions deux fois plus vite que vous pourriez dire que ce sont vous ne vous souciez pas des cas extrêmes, mais avec une définition aussi floue, nous pourrions aussi bien appeler un new Random().Next()référentiel transparent parce que bon sang, je me moque du nombre que je reçois de toute façon.
Sara
@kai: Selon le contexte, vous pouvez ignorer les effets secondaires. Par ailleurs, la valeur renvoyée par une fonction comme aléatoire n’est pas un effet secondaire: c’est son effet principal.
Giorgio
@Giorgio Random.Nextin .NET a effectivement des effets secondaires. Tout à fait. Si vous le pouvez Next, attribuez-la à une variable, puis rappelez-la Nextet affectez-la à une autre variable. Il est probable qu'elles ne seront pas égales. Pourquoi? Parce que l'invocation Nextchange quelque état interne caché dans l' Randomobjet. C'est l'opposé de la transparence référentielle. Je ne comprends pas votre affirmation selon laquelle les "effets principaux" ne peuvent être des effets secondaires. Dans le code impératif, il est plus courant que l'effet principal soit un effet secondaire, car les programmes impératifs sont par nature dynamiques.
Sara
3

Je ne sais pas comment on appelle de telles fonctions (ni même s'il y a même un nom systématique), mais j'appellerais une fonction qui n'est pas pure (comme les autres réponses sont étouffées) mais qui retourne toujours le même résultat si elle est fournie avec les mêmes paramètres " paramètres "(comparé à la fonction de ses paramètres et à un autre état). J'appellerais cela juste une fonction, mais malheureusement, lorsque nous parlons de "fonction" dans le contexte de la programmation, nous entendons quelque chose qui ne doit pas nécessairement être une fonction réelle.

utilisateur470365
la source
1
D'accord! C'est (officieusement) la définition mathématique de "fonction", mais comme vous dites, malheureusement, "fonction" signifie quelque chose de différent dans les langages de programmation, où il est plus proche de "une procédure étape par étape requise pour obtenir une valeur".
Andres F.
2

Cela dépend fondamentalement de si vous vous souciez de l'impureté ou non. Si la sémantique de cette table est que vous ne vous souciez pas du nombre d'entrées, alors c'est pur. Sinon, ce n'est pas pur.

Autrement dit, tant que les optimisations basées sur la pureté n'enfreignent pas la sémantique du programme, c'est bien.

Un exemple plus réaliste serait si vous essayiez de déboguer cette fonction et d’ajouter des instructions de journalisation. Techniquement, la journalisation est un effet secondaire. Les bûches le rendent-il impur? Non.

DeadMG
la source
En fait ça dépend. Peut-être que les journaux le rendent impur, par exemple, si vous vous souciez du nombre de fois et à quelle heure, "INFO f () appelé" apparaît dans votre journal. Ce que vous faites souvent :)
Andres F.
8
-1 Les journaux importent. Sur la plupart des plates-formes, la sortie de tout type synchronise implicitement le fil d'exécution. Le comportement de votre programme dépend des écritures d'autres threads, des rédacteurs de journaux externes, parfois des lectures de journaux, de l'état des descripteurs de fichiers. C'est aussi pur qu'un seau de terre.
Basilevs
@AndresF. Eh bien, vous ne vous souciez probablement pas du nombre de fois littéral. Vous ne devez probablement vous soucier que de la consigner autant de fois que la fonction a été appelée.
DeadMG
@Basilevs Le comportement de la fonction ne dépend pas d'eux. Si l'écriture du journal échoue, vous continuez tout droit.
DeadMG
2
Que vous choisissiez de définir le consignateur comme faisant partie de l'environnement d'exécution ou pas. Pour un autre exemple, ma fonction pure est-elle toujours pure si j'attache un débogueur au processus et lui crée un point d'arrêt? D'après le point de vue de la personne utilisant le débogueur, la fonction a clairement des effets secondaires, mais normalement, nous analysons un programme avec la convention que cela "ne compte pas". La même chose peut (mais pas nécessairement) être utilisée pour la journalisation utilisée pour le débogage, ce qui, je suppose, est la raison pour laquelle trace cache son impureté. La journalisation critique, par exemple pour l'audit, est bien sûr un effet secondaire important.
Steve Jessop
1

Je dirais que la meilleure chose à demander n'est pas comment nous l'appellerions, mais comment nous analyserions un tel code. Et ma première question clé dans une telle analyse serait:

  • L'effet secondaire dépend-il de l'argument de la fonction ou du résultat de l'effet secondaire?
    • Non: la "fonction efficace" peut être reformulée en une fonction pure, une action efficace et un mécanisme permettant de les combiner.
    • Oui: la "fonction efficace" est une fonction qui produit un résultat monadique.

Ceci est simple à illustrer dans Haskell (et cette phrase n’est qu’une demi-plaisanterie). Un exemple du cas "non" serait quelque chose comme ceci:

double :: Num a => a -> IO a
double x = do
  putStrLn "I'm doubling some number"
  return (x*2)

Dans cet exemple, l'action que nous effectuons (imprimer la ligne "I'm doubling some number") n'a aucun impact sur la relation entre xet le résultat. Cela signifie que nous pouvons le refactoriser de cette manière (en utilisant la Applicativeclasse et son *>opérateur), ce qui montre que la fonction et l'effet sont en fait orthogonaux:

double :: Num a => a -> IO a
double x = action *> pure (function x)
  where
    -- The pure function 
    function x = x*2  
    -- The side effect
    action = putStrLn "I'm doubling some number"

Donc, dans ce cas, je dirais personnellement que c'est un cas où vous pouvez factoriser une fonction pure. Cela concerne beaucoup de programmes Haskell - apprendre à décomposer les parties pures du code efficace.

Un exemple du type "oui", où les parties pures et effectives ne sont pas orthogonales:

double :: Num a => a -> IO a
double x = do
  putStrLn ("I'm doubling the number " ++ show x)
  return (x*2)

Maintenant, la chaîne que vous imprimez dépend de la valeur de x. La partie fonction (multiplier xpar deux), cependant, ne dépend pas du tout de l'effet, nous pouvons donc la factoriser:

logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
  logger a
  return (function a)

double x = logged function logger
  where function = (*2) 
        logger x putStrLn ("I'm doubling the number " ++ show x)

Je pourrais continuer à donner d’autres exemples, mais j’espère que c’est suffisant pour illustrer le point que j’avais commencé: vous n’appelez pas quelque chose, vous analysez la relation entre les parties pure et efficace et les factorisez quand c’est le cas. à votre avantage.

C'est l'une des raisons pour lesquelles Haskell utilise Monadtellement sa classe. Les monades sont (entre autres) un outil pour effectuer ce type d’analyse et de refactorisation.

sacundim
la source
-2

Les fonctions destinées à provoquer des effets secondaires sont souvent qualifiées d' effet . Exemple https://slpopejoy.github.io/posts/Effectful01.html

Ben Hutchison
la source
Seule réponse, mentionner le terme largement reconnu efficace et qui devient votant ... L'ignorance est un bonheur, je suppose. ..
Ben Hutchison
"efficace" est un mot que l'auteur de ce post a co-opté pour signifier "ayant des effets secondaires". Il le dit lui-même.
Robert Harvey
Une fonction efficace sur Google révèle de nombreuses preuves que son terme est largement utilisé. L'article de blog a été donné à titre d'exemple, et non à titre de définition. Dans les cercles de programmation fonctionnels où les fonctions pures sont les fonctions par défaut, il est nécessaire de disposer d’un terme positif pour décrire les fonctions ayant des effets secondaires indésirables. C'est plus que la simple absence de pureté . Ce terme est efficace. Maintenant, considérez-vous instruit.
Ben Hutchison
Meh
Robert Harvey