La programmation fonctionnelle dans Scala explique l'impact d'un effet secondaire sur la rupture de la transparence référentielle:
effet secondaire, ce qui implique une certaine violation de la transparence référentielle.
J'ai lu une partie du SICP , qui traite de l'utilisation du «modèle de substitution» pour évaluer un programme.
Comme je comprends à peu près le modèle de substitution avec transparence référentielle (RT), vous pouvez décomposer une fonction dans ses parties les plus simples. Si l'expression est RT, vous pouvez décomposer l'expression et obtenir toujours le même résultat.
Cependant, comme l'indique la citation ci-dessus, l'utilisation d'effets secondaires peut / cassera le modèle de substitution.
Exemple:
val x = foo(50) + bar(10)
Si foo
et bar
n'ont pas d'effets secondaires, l'exécution de l'une ou l'autre fonction renvoie toujours le même résultat à x
. Mais, s'ils ont des effets secondaires, ils modifieront une variable qui perturbe / jette une clé dans le modèle de substitution.
Je me sens à l'aise avec cette explication, mais je ne la comprends pas complètement.
Veuillez me corriger et remplir tous les trous en ce qui concerne les effets secondaires brisant la RT, en discutant également les effets sur le modèle de substitution.
la source
RT
vous empêche donc d'utiliser le.substitution model.
Le gros problème de ne pas pouvoir l'utilisersubstitution model
est le pouvoir de l'utiliser pour raisonner sur un programme?Imaginez que vous essayez de construire un mur et que vous avez reçu un assortiment de boîtes de différentes tailles et formes. Vous devez remplir un trou en forme de L particulier dans le mur; devriez-vous chercher une boîte en forme de L ou pouvez-vous remplacer deux boîtes droites de la taille appropriée?
Dans le monde fonctionnel, la réponse est que l'une ou l'autre solution fonctionnera. Lors de la construction de votre monde fonctionnel, vous n'avez jamais à ouvrir les boîtes pour voir ce qu'il y a à l'intérieur.
Dans le monde impératif, il est dangereux de construire votre mur sans inspecter le contenu de chaque boîte et les comparer au contenu de toutes les autres:
Je pense que je vais m'arrêter avant de perdre votre temps avec des métaphores plus improbables, mais j'espère que le point est fait; les briques fonctionnelles ne contiennent aucune surprise cachée et sont entièrement prévisibles. Parce que vous pouvez toujours utiliser des blocs plus petits de la bonne taille et de la bonne forme pour remplacer un plus grand et qu'il n'y a pas de différence entre deux boîtes de la même taille et de la même forme, vous disposez d'une transparence référentielle. Avec des briques impératives, il ne suffit pas d'avoir quelque chose de la bonne taille et de la bonne forme - il faut savoir comment la brique a été construite. Pas référentiellement transparent.
Dans un langage fonctionnel pur, tout ce que vous devez voir est la signature d'une fonction pour savoir ce qu'elle fait. Bien sûr, vous voudrez peut-être regarder à l'intérieur pour voir comment il fonctionne, mais vous n'avez pas besoin de regarder.
Dans un langage impératif, on ne sait jamais quelles surprises pourraient se cacher à l'intérieur.
la source
(a, b) -> a
ne peut être que lafst
fonction et qu'une fonction de typea -> a
ne peut être que laidentity
fonction, mais vous ne pouvez pas nécessairement dire quoi que ce soit à propos d'une fonction de type(a, a) -> a
, par exemple.Oui, l'intuition a tout à fait raison. Voici quelques conseils pour être plus précis:
Comme vous l'avez dit, toute expression RT doit avoir un
single
"résultat". Autrement dit, étant donné unefactorial(5)
expression dans le programme, il devrait toujours donner le même "résultat". Donc, si un certainfactorial(5)
est dans le programme et qu'il donne 120, il devrait toujours donner 120 quel que soit "l'ordre des étapes", il est développé / calculé - quelle que soit l' heure .Exemple: la
factorial
fonction.Il y a quelques considérations avec cette explication.
Tout d'abord, gardez à l'esprit que les différents modèles d'évaluation (voir ordre applicatif vs ordre normal) peuvent donner des "résultats" différents pour la même expression RT.
Dans le code ci-dessus,
first
etsecond
sont référentiellement transparents, et pourtant, l'expression à la fin donne des "résultats" différents si elle est évaluée dans l'ordre normal et l'ordre d'application (sous ce dernier, l'expression ne s'arrête pas)..... ce qui conduit à l'utilisation de "résultat" entre guillemets. Puisqu'il n'est pas nécessaire qu'une expression s'arrête, elle peut ne pas produire de valeur. Donc, utiliser "résultat" est un peu flou. On peut dire qu'une expression RT donne toujours la même chose
computations
dans un modèle d'évaluation.Troisièmement, il peut être nécessaire de voir deux
foo(50)
apparaitre dans le programme à des endroits différents comme des expressions différentes - chacune produisant ses propres résultats qui peuvent différer les uns des autres. Par exemple, si le langage autorise une portée dynamique, les deux expressions, bien que lexicalement identiques, sont différentes. En perl:La portée dynamique induit en erreur car elle permet de penser facilement que
x
c'est la seule entrée pourfoo
, alors qu'en réalité, c'estx
ety
. Une façon de voir la différence est de transformer le programme en programme équivalent sans portée dynamique - c'est-à-dire en passant explicitement les paramètres, donc au lieu de définirfoo(x)
, nous définissonsfoo(x, y)
et passonsy
explicitement dans les appelants.Le fait est que nous sommes toujours dans un
function
état d'esprit: étant donné une certaine entrée pour une expression, nous recevons un "résultat" correspondant. Si nous donnons la même entrée, nous devrions toujours nous attendre au même "résultat".Maintenant, qu'en est-il du code suivant?
La
foo
procédure rompt RT car il y a des redéfinitions. Autrement dit, nous avons définiy
en un point, et ce dernier sur, redéfini ce mêmey
. Dans l'exemple perl ci-dessus, lesy
s sont des liaisons différentes bien qu'ils partagent le même nom de lettre "y". Ici, lesy
s sont en fait les mêmes. C'est pourquoi nous disons que la (ré) affectation est une méta- opération: vous changez en fait la définition de votre programme.En gros, les gens décrivent généralement la différence comme suit: dans un environnement sans effets secondaires, vous avez une cartographie à partir de
input -> output
. Dans un cadre «impératif», vous avezinput -> ouput
dans le cadre d'unstate
qui peut évoluer dans le temps.Maintenant, au lieu de simplement substituer des expressions à leurs valeurs correspondantes, il faut également appliquer des transformations
state
à chaque opération qui l'exige (et bien sûr, les expressions peuvent les consulterstate
pour effectuer des calculs).Donc, si dans un programme libre d'effets secondaires, tout ce que nous devons savoir pour calculer une expression est son entrée individuelle, dans un programme impératif, nous devons connaître les entrées et l'état entier, pour chaque étape de calcul. Le raisonnement est le premier à subir un gros coup (maintenant, pour déboguer une procédure problématique, il faut l'entrée et le core dump). Certaines astuces sont rendues impraticables, comme la mémorisation. Mais aussi, la concurrence et le parallélisme deviennent beaucoup plus difficiles.
la source