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.
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é une factorial(5)
expression dans le programme, il devrait toujours donner le même "résultat". Donc, si un certain factorial(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.
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
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.
def first(y, z):
return y
def second(x):
return second(x)
first(2, second(3)) # result depends on eval. model
Dans le code ci-dessus, first
et second
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:
sub foo {
my $x = shift;
return $x + $y; # y is dynamic scope var
}
sub a {
local $y = 10;
return &foo(50); # expanded to 60
}
sub b {
local $y = 20;
return &foo(50); # expanded to 70
}
La portée dynamique induit en erreur car elle permet de penser facilement que x
c'est la seule entrée pour foo
, alors qu'en réalité, c'est x
et y
. 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éfinir foo(x)
, nous définissons foo(x, y)
et passons y
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?
def foo():
global y
y = y + 1
return y
y = 10
foo() # yields 11
foo() # yields 12
La foo
procédure rompt RT car il y a des redéfinitions. Autrement dit, nous avons défini y
en un point, et ce dernier sur, redéfini ce même y
. Dans l'exemple perl ci-dessus, les y
s sont des liaisons différentes bien qu'ils partagent le même nom de lettre "y". Ici, les y
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 avez input -> ouput
dans le cadre d'un state
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 consulter state
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.
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?