Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[71]

((lambda (amount) (- 25 amount)) 20)

Теперь мы применяем оператор к операнду, подставляя 20 вместо amount в теле lambda-выра-жения:

(- 25 20)

Окончательный результат равен 5.

Посмотрим, однако, что произойдет, если мы попробуем применить подобный подстановочный анализ к make-simplified-withdraw:

((make-simplified-withdraw 25) 20)

Сначала мы упрощаем оператор, подставляя вместо balance 25 в теле make-simplified-withdraw. Таким образом, наше выражение сводится к9

((lambda (amount) (set! balance (- 25 amount)) 25) 20)

Теперь мы применяем оператор к операнду, подставляя в теле lambda-выраже-ния 20 вместо amount:

(set! balance (- 25 20)) 25

Если бы мы следовали подстановочной модели, нам пришлось бы сказать, что вычисление процедуры состоит в том, чтобы сначала присвоить переменной balance значение 5, а затем в качестве значения вернуть 25. Но это дает неверный ответ. Чтобы получить правильный ответ, нам пришлось бы как-то отличить первое вхождение balance (до того, как сработает set! ) от второго (после выполнения set! ). Подстановочная модель на это не способна.

Проблема здесь состоит в том, что подстановка предполагает, что символы в нашем языке - просто имена для значений. Но как только мы вводим set! и представление, что значение переменной может изменяться, переменная уже не может быть всего лишь именем. Теперь переменная некоторым образом соответствует месту, в котором может храниться значение, и значение это может меняться. В разделе 3.2 мы увидим, как в нашей модели вычислений роль этого «места» играют окружения.

Тождественность и изменение

Проблема, который здесь встает, глубже, чем просто поломка определенной модели вычислений. Как только мы вводим в наши вычислительные модели понятие изменения, многие другие понятия, которые до сих пор были ясны, становятся сомнительными. Рассмотрим вопрос, что значит, что две вещи суть «одно и то же».

Допустим, мы два раза зовем make-decrementer с одним и тем же аргументом, и получаем две процедуры:

(define Di (make-decrementer 25)) (define D2 (make-decrementer 25))

9Мы не производим подстановку вхождения balance в выражение set!, поскольку {имя) в set! не вычисляется. Если бы мы провели подстановку, получилось бы (set! 25 (-25 amount)), а это не имеет никакого смысла.


Являются ли D1 и D2 одним и тем же объектом? Можно сказать, что да, поскольку D1 и D2 обладают одинаковым поведением - каждая из этих процедур вычитает свой аргумент из 25. В сущности, в любом вычислении можно подставить D1 вместо D2, и результат не изменится.

Напротив, рассмотрим два вызова make-simplified-withdraw:

(define W1 (make-simplified-withdraw 25)) (define W2 (make-simplified-withdraw 25))

Являются ли W1 и W2 одним и тем же? Нет, конечно, потому что вызовы W1 и W2 приводят к различным результатам, как показывает следующая последовательность вычислений:

(W1 20)

5

(W1 20)

-15

(W2 20) 5

Хотя W1 и W2 «равны друг другу» в том смысле, что оба они созданы вычислением одного и того же выражения (make-simplified-withdraw 25),

неверно, что в любом выражении можно заменить W1 на W2, не повлияв при этом на результат его вычисления.

Язык, соблюдающий правило, что в любом выражении «одинаковое можно подставить вместо одинакового», не меняя его значения, называется референ-циально прозрачным (referentially transparent). Если мы включаем в свой компьютерный язык set!, его референциальная прозрачность нарушается. Становится сложно определить, где можно упростить выражение, подставив вместо него равносильное. Следовательно, рассуждать о программах, в которых используется присваивание, оказывается гораздо сложнее.

С потерей референциальной прозрачности становится сложно формально описать понятие о том, что два объекта - один и тот же объект. На самом деле, смысл выражения «то же самое» в реальном мире, который наши программы моделируют, сам по себе недостаточно ясен. В общем случае, мы можем проверить, являются ли два как будто бы одинаковых объекта одним и тем же, только изменяя один из них и наблюдая, изменился ли таким же образом и другой. Но как мы можем узнать, «изменился» ли объект? Только рассмотрев один и тот же объект дважды и проверив, не различается ли некоторое его свойство между двумя наблюдениями. Таким образом, мы не можем определить «изменение», не имея заранее понятия «идентичности», а идентичность мы не можем определить, не рассмотрев результаты изменений.

В качестве примера того, как эти вопросы возникают в программировании, рассмотрим ситуацию, где у Петра и у Павла есть по банковскому счету в 100 долларов. Здесь не все равно, смоделируем мы это через

(define peter-acc (make-account 100)) (define paul-acc (make-account 100))


(define peter-acc (make-account i00)) (define paul-acc peter-acc)

В первом случае, два счета различны. Действия, которые производит Петр, не меняют счет Павла, и наоборот. Однако во втором случае мы сказали, что paul-acc - это та же самая вещь, что и peter-acc. Теперь у Петра и у Павла есть совместный банковский счет, и если Петр возьмет сколько-то с peter-acc, то у Павла на paul-acc будет меньше денег. При построении вычислительных моделей сходство между этими двумя несовпадающими ситуациями может привести к путанице. В частности, в случае с совместным счетом может особенно мешать то, что у одного объекта (банковского счета) есть два имени (peter-acc и paul-acc); если мы ищем в программе все места, где может меняться paul-acc, надо смотреть еще и где меняется peter-acc.10

В связи с этими замечаниями обратите внимание на то, что если бы Петр и Павел могли только проверять свой платежный баланс, но не менять его, то вопрос «один ли у них счет?» не имел бы смысла. В общем случае, если мы никогда не меняем объекты данных, то можно считать, что каждый объект представляет собой в точности совокупность своих частей. Например, рациональное число определяется своим числителем и знаменателем. Однако при наличии изменений такой взгляд становится ошибочным, поскольку теперь у каждого объекта есть «индивидуальность», которая отличается от тех частей, из которых он состоит. Банковский счет останется «тем же самым» счетом, даже если мы снимем с него часть денег; и наоборот, можно иметь два разных счета с одинаковым состоянием. Такие сложности - следствие не нашего языка программирования, а нашего восприятия банковского счета как объекта. Скажем, рациональное число мы обычно не рассматриваем как изменяемый объект со своей индивидуальностью, у которого можно было бы изменить числитель и по-прежнему иметь дело с «тем же» числом.

Ловушки императивного программирования

В противоположность функциональному программированию, стиль программирования, при котором активно используется присваивание, называется императивное программирование (imperative programming). Кроме того, что возникают сложности с вычислительными моделями, программы, написанные в императивном стиле, подвержены таким ошибкам, которые в функциональных программах не возникают. Вспомним, к примеру, итеративную программу для вычисления факториала из раздела 1.2.1:

(define (factorial n)

(define (iter product counter) (if (> counter n) product

10Когда у вычислительного объекта имеется несколько имён, эти имена называются псевдонимами (aliasing). Ситуация с совместным банковским счетом - простой пример псевдонимов. В разделе 3.3 мы увидим значительно более сложные примеры, скажем, «различные» составные структуры с общими частями. Если мы забудем, что «побочным эффектом» в результате изменения одного объекта может стать изменение «другого» объекта, поскольку «разные» объекты - на самом деле один и тот же под разными псевдонимами, то могут возникнуть ошибки. Эти так называемые ошибки побочных эффектов (side-effect bugs) настолько трудно обнаруживать и анализировать, что некоторые исследователи выступали с предложениями не допускать в языках программирования побочные эффекты и псевдонимы (Lampson et al. 1981; Morris, Schmidt, and Wadler 1980).



[стр.Начало] [стр.1] [стр.2] [стр.3] [стр.4] [стр.5] [стр.6] [стр.7] [стр.8] [стр.9] [стр.10] [стр.11] [стр.12] [стр.13] [стр.14] [стр.15] [стр.16] [стр.17] [стр.18] [стр.19] [стр.20] [стр.21] [стр.22] [стр.23] [стр.24] [стр.25] [стр.26] [стр.27] [стр.28] [стр.29] [стр.30] [стр.31] [стр.32] [стр.33] [стр.34] [стр.35] [стр.36] [стр.37] [стр.38] [стр.39] [стр.40] [стр.41] [стр.42] [стр.43] [стр.44] [стр.45] [стр.46] [стр.47] [стр.48] [стр.49] [стр.50] [стр.51] [стр.52] [стр.53] [стр.54] [стр.55] [стр.56] [стр.57] [стр.58] [стр.59] [стр.60] [стр.61] [стр.62] [стр.63] [стр.64] [стр.65] [стр.66] [стр.67] [стр.68] [стр.69] [стр.70] [стр.71] [стр.72] [стр.73] [стр.74] [стр.75] [стр.76] [стр.77] [стр.78] [стр.79] [стр.80] [стр.81] [стр.82] [стр.83] [стр.84] [стр.85] [стр.86] [стр.87] [стр.88] [стр.89] [стр.90] [стр.91] [стр.92] [стр.93] [стр.94] [стр.95] [стр.96] [стр.97] [стр.98] [стр.99] [стр.100] [стр.101] [стр.102] [стр.103] [стр.104] [стр.105] [стр.106] [стр.107] [стр.108] [стр.109] [стр.110] [стр.111] [стр.112] [стр.113] [стр.114] [стр.115] [стр.116] [стр.117] [стр.118] [стр.119] [стр.120] [стр.121] [стр.122] [стр.123] [стр.124] [стр.125] [стр.126] [стр.127] [стр.128] [стр.129] [стр.130] [стр.131] [стр.132] [стр.133] [стр.134] [стр.135] [стр.136] [стр.137] [стр.138] [стр.139] [стр.140] [стр.141] [стр.142] [стр.143] [стр.144] [стр.145] [стр.146] [стр.147] [стр.148] [стр.149] [стр.150] [стр.151] [стр.152] [стр.153] [стр.154] [стр.155] [стр.156] [стр.157] [стр.158] [стр.159] [стр.160] [стр.161] [стр.162] [стр.163] [стр.164] [стр.165] [стр.166] [стр.167] [стр.168] [стр.169] [стр.170] [стр.171] [стр.172] [стр.173] [стр.174] [стр.175] [стр.176] [стр.177] [стр.178] [стр.179] [стр.180] [стр.181] [стр.182] [стр.183] [стр.184] [стр.185] [стр.186] [стр.187] [стр.188] [стр.189] [стр.190] [стр.191] [стр.192] [стр.193] [стр.194] [стр.195] [стр.196]