Поскольку я приблизительно понимаю модель замещения (с прозрачностью ссылок (RT)), вы можете разложить функцию на ее самые простые части. Если выражение RT, вы можете декомпозировать выражение и всегда получать один и тот же результат.
Да, интуиция совершенно права. Вот несколько указателей, чтобы получить более точную информацию:
Как вы сказали, любое выражение RT должно иметь single
«результат». То есть, учитывая factorial(5)
выражение в программе, оно всегда должно давать один и тот же «результат». Таким образом, если определенное число factorial(5)
находится в программе и оно дает 120, оно всегда должно давать 120 независимо от того, какой «порядок шагов» он расширяет / вычисляет - независимо от времени .
Пример: factorial
функция.
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
Есть несколько соображений с этим объяснением.
Прежде всего, имейте в виду, что разные модели оценки (см. Аппликативный и нормальный порядок) могут давать разные «результаты» для одного и того же выражения RT.
def first(y, z):
return y
def second(x):
return second(x)
first(2, second(3)) # result depends on eval. model
В приведенном выше коде, first
и second
являются референциально прозрачными, и тем не менее, выражение в конце дает разные «результаты» , если оценены при нормальном порядке и аппликативном порядке ( в соответствии с последним, выражение не останавливается).
.... что приводит к использованию "результата" в кавычках. Поскольку для остановки выражения не требуется, оно может не давать значения. Таким образом, использование «результата» довольно размыто. Можно сказать, что выражение RT всегда дает то же самое computations
при модели оценки.
В-третьих, может потребоваться, чтобы два приложения foo(50)
появлялись в программе в разных местах как разные выражения - каждое из них давало свои собственные результаты, которые могли бы отличаться друг от друга. Например, если язык допускает динамическую область видимости, оба выражения, хотя и лексически идентичные, различны. В 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
}
Динамический охват вводит в заблуждение, потому что он позволяет легко думать, что x
это единственный вход для foo
, когда в действительности это так x
и есть y
. Один из способов увидеть разницу состоит в том, чтобы преобразовать программу в эквивалентную без динамической области видимости - то есть, передавая явно параметры, поэтому вместо определения foo(x)
мы определяем foo(x, y)
и передаем y
явно в вызывающих программах.
Дело в том, что мы всегда находимся в function
мышлении: учитывая определенный вклад в выражение, нам дают соответствующий «результат». Если мы даем один и тот же вклад, мы всегда должны ожидать один и тот же «результат».
А как насчет следующего кода?
def foo():
global y
y = y + 1
return y
y = 10
foo() # yields 11
foo() # yields 12
foo
Процедура ломает RT , потому что есть переопределения. То есть, мы определили y
в одной точке, а затем переопределили то же самое y
. В приведенном выше примере perl, y
s - это разные привязки, хотя они имеют одно и то же буквенное имя «y». Здесь y
с на самом деле то же самое. Вот почему мы говорим, что (пере) присваивание является мета- операцией: вы фактически меняете определение своей программы.
Грубо говоря, люди обычно изображают разницу следующим образом: в установке без побочных эффектов у вас есть отображение input -> output
. В «императивной» обстановке у вас есть input -> ouput
контекст, state
который может меняться со временем.
Теперь вместо того, чтобы просто подставлять выражения для их соответствующих значений, нужно также применять преобразования к state
каждой операции, которая в этом нуждается (и, конечно, выражения могут обращаться state
к ним для выполнения вычислений).
Таким образом, если в программе, свободной от побочных эффектов, все, что нам нужно знать для вычисления выражения, это его индивидуальный ввод, в императивной программе нам нужно знать входные данные и все состояние для каждого шага вычисления. Рассуждение является первым ударом по большому счету (теперь, чтобы отладить проблемную процедуру, вам нужны входные данные и дамп ядра). Некоторые трюки оказываются непрактичными, как запоминание. Но также параллелизм и параллелизм становятся гораздо более сложными.
RT
отключает вас от использованияsubstitution model.
. Большая проблема с невозможностью использованияsubstitution model
- это сила использования его для рассуждения о программе?