Поскольку я приблизительно понимаю модель замещения (с прозрачностью ссылок (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, ys - это разные привязки, хотя они имеют одно и то же буквенное имя «y». Здесь yс на самом деле то же самое. Вот почему мы говорим, что (пере) присваивание является мета- операцией: вы фактически меняете определение своей программы.
Грубо говоря, люди обычно изображают разницу следующим образом: в установке без побочных эффектов у вас есть отображение input -> output. В «императивной» обстановке у вас есть input -> ouputконтекст, stateкоторый может меняться со временем.
Теперь вместо того, чтобы просто подставлять выражения для их соответствующих значений, нужно также применять преобразования к stateкаждой операции, которая в этом нуждается (и, конечно, выражения могут обращаться stateк ним для выполнения вычислений).
Таким образом, если в программе, свободной от побочных эффектов, все, что нам нужно знать для вычисления выражения, это его индивидуальный ввод, в императивной программе нам нужно знать входные данные и все состояние для каждого шага вычисления. Рассуждение является первым ударом по большому счету (теперь, чтобы отладить проблемную процедуру, вам нужны входные данные и дамп ядра). Некоторые трюки оказываются непрактичными, как запоминание. Но также параллелизм и параллелизм становятся гораздо более сложными.
RTотключает вас от использованияsubstitution model.. Большая проблема с невозможностью использованияsubstitution model- это сила использования его для рассуждения о программе?