web-dev-qa-db-fra.com

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

Disons que nous avons une fonction pure normale telle que

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

Et puis nous le modifions de telle sorte qu'il a un effet secondaire

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

Pour 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 pour autant qu'il renvoie la même sortie pour les mêmes entrées.

Y a-t-il un nom différent pour ce type de fonction, est-il sans nom ou est-il toujours pur et je me trompe sur la définition de la pureté?

43
m0meni

Je ne suis pas sûr des définitions universelles de la pureté, mais du point de vue de Haskell (un langage où les programmeurs ont tendance à se soucier 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 add n'est pas pure. Donc, en réponse à votre question, je l'appellerais "impure";)

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

  1. Ne dépend que de son entrée. Autrement dit, étant donné la même entrée, il retournera toujours la même sortie.
  2. Est référentiellement transparent: la fonction peut être librement remplacée 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 renvoient la même valeur, la fonction f écrit deux fois dans la base de données mais g écrit une fois! Il est très probable que les écritures dans la base de données font partie du comportement observable de votre programme, auquel cas j'ai montré que votre deuxième version de add n'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 add peuvent être considérées comme équivalentes et pures. Mais je ne peux pas penser à un scénario où l'écriture dans la base de données n'a pas d'importance. Même la journalisation est importante!

84
Andres F.

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

Une telle fonction est appelée

déterministe

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

termwiki.com

Concernant l'état:

Selon la définition d'une fonction que vous utilisez, une fonction n'a pas d'état. Si vous venez du monde orienté objet, rappelez-vous que x.f(y) est une méthode. En tant que fonction, cela ressemblerait à f(x,y). Et si vous êtes dans des fermetures à portée lexicale fermée, souvenez-vous que l'état immuable pourrait tout aussi bien faire partie de l'expression des fonctions. Ce n'est qu'un é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 toutes deux déterministes. Votre premier est également une fonction pure. Votre deuxième n'est pas pur.

Fonction pure

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

  2. L'évaluation du résultat ne provoque aucun effet secondaire ou sortie sémantiquement observable, comme une mutation d'objets mutables ou une sortie vers des dispositifs 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 permet uniquement à ses arguments et à sa valeur renvoyée de changer. Rien d'autre ne provoque le changement. Rien d'autre n'est changé.

18
candied_orange

Si vous ne vous souciez pas de l'effet secondaire, il est référentiellement transparent. Bien sûr, il est possible que vous ne vous en souciez pas, mais quelqu'un d'autre le fait, donc l'applicabilité du terme dépend 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 sont ceux qui sont idempotents. En informatique, un peu différemment qu'en mathématiques *, une fonction idempotente est celle qui peut être répétée avec le même effet; c'est-à-dire que l'effet secondaire 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 exactement cohérent, alors ce serait idempotent, mais s'il s'ajoutait à la base de données, ou ajouté à un fichier, alors il ne le ferait pas.

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

* L'utilisation de idempotent différemment en informatique qu'en mathématiques semble provenir d'une mauvaise utilisation du terme mathématique qui a ensuite été adopté car le concept est utile.

9
Jon Hanna

Je ne sais pas comment ces fonctions sont appelées (ou s'il y a même un nom systématique), mais j'appellerais une fonction qui n'est pas pure (comme d'autres réponses recroquevillées) mais renvoie toujours le même résultat si elle est fournie avec les mêmes paramètres. paramètres "(par rapport à la fonction de ses paramètres et d'un autre état). Je l'appellerais simplement fonction, mais malheureusement quand nous disons "fonction" dans le contexte de la programmation, nous voulons dire quelque chose qui n'a pas du tout à être une fonction réelle.

3
user470365

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

Ou pour le dire autrement, c'est bien tant que les optimisations basées sur la pureté ne cassent pas la sémantique du programme.

Un exemple plus réaliste serait si vous essayez de déboguer cette fonction et d'ajouter des instructions de journalisation. Techniquement, la journalisation est un effet secondaire. Les grumes le rendent-elles impure? Non.

2
DeadMG

Je dirais que la meilleure chose à demander n'est pas comment nous l'appellerions, mais comment analyser un tel morceau de 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 refactorisée en une fonction pure, une action efficace et un mécanisme pour les combiner.
    • Oui: La "fonction efficace" est une fonction qui produit un résultat monadique.

C'est simple à illustrer dans Haskell (et cette phrase n'est qu'une demi-blague). 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 entreprenons (imprimez la ligne "I'm doubling some number") n'a aucun impact sur la relation entre x et le résultat. Cela signifie que nous pouvons le refactoriser de cette façon (en utilisant la classe Applicative 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. Beaucoup de programmation Haskell est à ce sujet - apprendre à factoriser 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 fonction (multiplier x par deux), cependant, ne dépend pas du tout de l'effet, donc nous pouvons toujours factoriser IT out:

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 à énoncer d'autres exemples, mais j'espère que cela suffit pour illustrer le point de départ: vous ne "l'appelez" pas quelque chose, vous analysez comment les parties pures et efficaces se rapportent et les factorisez quand c'est à votre avantage.

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

1
sacundim