├── README └── chapter1_procedures.rst /README: -------------------------------------------------------------------------------- 1 | В репозитории хранятся конспекты учебника "Структура и интерпретация компьютерных программ (2-е издание). Авторы: Харольд Абельсон, Джеральд Джей Сассман" 2 | -------------------------------------------------------------------------------- /chapter1_procedures.rst: -------------------------------------------------------------------------------- 1 | 1.1 Элементы программирования 2 | ======================== 3 | 4 | Всякий язык программирования обладает тремя механизмами: 5 | 6 | * элементарные выражения, представляющий элементарные сущности, с которым язык имеет дело 7 | * средства комбинирования, с помощью которых из простых объектов составляются сложные 8 | * средства абстракции, с помощью которых сложные объекты можно называть и обращаться с ними как с единым целым. 9 | 10 | 1.1.1 Выражения 11 | ^^^^^^^^^^^^^^^ 12 | 13 | Выражения, образуемые путем заключения списка выражения в скобки с целью обозначить применение функции к аргументам, называются **комбинациями** (combinations). 14 | Самый левый элемент в списке называют **оператором** (operator), а остальные элементы - **операндами** (operands). Значение комбинации вычисляется путем применения процедуры, задаваемой оператором, к аргументами, которые являются значениями операндов. 15 | 16 | Примеры:: 17 | 18 | (+ 137 439) 19 | (* 5 99) 20 | (/ 10 2) 21 | 22 | Соглашение, по которому оператор ставится слева от операндов, известно как **префиксная нотация**. Несмотря на то, что префиксная нотация отличается от общепринятой математической записи, у нее есть свои преимущества. Одно из них состоит в том, что префиксная запись может распространяться на процедуры с произвольным количеством аргументов. 23 | Например:: 24 | 25 | (+ 21 35 12 7) 26 | (* 25 4 12) 27 | 28 | Второе преимущество префиксной нотации в том, что она естественным образом расширяется, позволяя комбинациям вкладываться друг в друга, т.е. допускает комбинации, элементы которых сами являются комбинациями. 29 | :: 30 | 31 | (+ (- 10 7) (* 2 8)) 32 | (- (* 5 (* 18 4 2)) (/ 8 (+ 4 10))) 33 | 34 | Из-за того, что последний пример тяжело читать, используется следующее правило форматирования 35 | :: 36 | 37 | (- (* 5 38 | (* 18 4 2)) 39 | (/ 8 40 | (+ 4 10))) 41 | 42 | Согласно этому правилу, комбинации записываются так, чтобы ее операнды выравнивались вертикально. 43 | 44 | 1.1.2. Имена и окружение 45 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 46 | 47 | В диалекте Лиспа Scheme мы даем объектам имена с помощью слова **define**. Предложение: 48 | :: 49 | 50 | (define size 2) 51 | 52 | заставляет интерпретатор связать значение 2 с именем size. После того, как имя size связано со значением 2, мы можем указывать на значение 2 с помощью имени: 53 | :: 54 | 55 | size 56 | 2 57 | 58 | Слово define служит в нашем языке простейшим средством абстракции, поскольку оно позволяет нам использовать простые имена для обозначения результатов сложных операций. 59 | 60 | Ясно, что раз интерпретатор способен ассоциировать значения с символами и затем вспоминать их, то он должен иметь некоторого рода память, сохраняющую пары имя-объект. Эта память называется окружением (enivironment), а точнее глобальным окружением (global environment). 61 | 62 | 1.1.3. Составные процедуры 63 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 64 | 65 | Общая форма определения процедуры такова: 66 | :: 67 | 68 | (define (имя <формальные параметры>)) (тело)) 69 | 70 | Имя - тот символ, с которым нужно связать в окружении определение процедуры. Параметры - имена, которые в теле процедуры используются для отсылки к соответствующим аргументам процедуры. Тело - это выражение, которое вычислит результат применения процедуры, когда параметры будут заменены аргументами, в которым процедура будет применяться. 71 | Например: 72 | :: 73 | 74 | (define (square x) (* x x)) 75 | (square 21) 76 | 441 77 | 78 | Кроме того, мы можем использовать square при определении других процедур. Например x^2 + y^2 можно записать следующим образом: 79 | :: 80 | 81 | (+ (square x) (square y)) 82 | (define (f a) 83 | (sum-of-squares (+ a 1) (* a 2))) 84 | 85 | 1.1.4. Подстановочная модель применения процедуры 86 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 87 | 88 | Чтобы применить составную процедуру к аргументам, требуется вычислить тело процедуры, заменив каждый формальный параметр соответствующим аргументом. 89 | Чтобы проиллюстрировать данный процесс, вычислим комбинацию: 90 | :: 91 | 92 | (f 5) 93 | 94 | где f - процедура, определенная в пред. разделе. Начнем с того, что восстанавливаем тело f: 95 | :: 96 | 97 | (sum-of-squares (+ a 1) (* a 2)) 98 | 99 | Затем, заменяем формальный параметр a на аргумент 5: 100 | :: 101 | 102 | (sum-of-squares (+ 5 1) (* 5 2)) 103 | 104 | Таким образом, задача сводится к вычислению комбинации с двумя операндами и оператором sum-of-squares. Вычисление этой комбинации включает три подзадачи. Нам нужно вычислить оператор, чтобы получить процедуру, которую требуется применить, а также операнды, чтобы получить аргументы. При этом (+ 5 1) дает 6, а (* 5 2) дает 10, так что нам требуется применить процедуру sum-of-squares к 6 и 10. Эти значения подставляются на место формальных параметров x и y в теле sum-of-squares, приводя выражение к 105 | :: 106 | 107 | (+ (square 6) (square 10)) 108 | 109 | Когда мы используем определение square, это приводится к: 110 | :: 111 | 112 | (+ (* 6 6) (* 10 10)) 113 | 114 | Что при умножении сводится к 115 | :: 116 | 117 | (+ 36 100) 118 | 119 | И наконец к: 120 | :: 121 | 122 | 136 123 | 124 | Только, что описанный нами процесс называется подстановочной моделью (substitution model). Ее можно использовать как модель, которая определяет смысл понятия применения процедуры. Имеются, однако, две детали, которые необходимо подчеркнуть: 125 | #. Цель подстановочной модели - помочь нам представить, как применяются процедуры, а не дать описание того, как на самом деле работает интерпретатор. 126 | #. На протяжении этой книги мы представим последовательность усложняющихся моделей того, как работает интерпретатор, завершающуюся полным воплощением интепретатора и компилятора. 127 | 128 | Аппликативный и нормальный порядки вычисления 129 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 130 | 131 | В соответствии с описанием из пред. раздела интерпретатор сначала вычисляет оператор и операнды, а затем применяет получившуюся процедуру к получившимся аргументам. Но это не единственный способ осуществлять вычисления. Другая модель вычисления не вычисляет аргументы, пока не понадобятся их значение. Вместо этого, она подставляет на место параметров выражения-операнды, пока не получит выражение, в котором присутствуют только элементарные операторы, и лишь затем вычисляет его. Если бы мы использовали этот метод, вычисление 132 | :: 133 | 134 | (f 5) 135 | 136 | прошло бы последовательность подстановок 137 | :: 138 | 139 | (sum-of-squares (+ 5 1) (* 5 2)) 140 | (+ (square (+ 5 1)) (square (* 5 2)) ) 141 | (+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2)) )) 142 | 143 | За которыми последуют редукции: 144 | :: 145 | 146 | (+ (* 6 6) (* 10 10)) 147 | (+ 36 100) 148 | 136 149 | 150 | Это дает тот же результат, что и предыдущая модель вычислений, но процесс его получения отличается. 151 | Альтернативный метод "полная подстановка, затем редукция" известен под названием нормальный порядок вычислений (normal-order evaluation), в противовес методу "вычисление аргументов, затем применение процедуры", которое называется аппликативным порядком вычислений (applicative-order evaluation). 152 | 153 | В Лиспе используется аппликативный порядок вычисления, отчасти из-за дополнительной эффективности, которую дает возможность не вычислять многократно выражения, а отчасти, что важнее, потому что с нормальным порядком вычислений становится очень сложно обращаться, как только мы покидаем область процедур, которые можно смоделировать с помощью подстановки. С другой стороны, нормальный порядок вычисления, может быть весьма ценным инструментом, и некоторые его применения мы рассмотрим позже. 154 | 155 | Условные выражения и предикаты 156 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 157 | В Лиспе существует особая форма для обозначения разбора случаев. Она называется cond и используется так: 158 | :: 159 | 160 | (define (abs x) 161 | (cond ((> x 0) x) 162 | ((= x 0) 0) 163 | ((< x 0) (-x)))) 164 | 165 | Общая форма условного выражения такова: 166 | :: 167 | 168 | (cond ( ) 169 | ( ) 170 | ... 171 | ( )) 172 | 173 | Она состоит из символа cond, за которым следуют заключенные в скобки пары выражений (

), называемых ветвями (clauses). В каждой из этих пар, первое выражение предикат, то есть выражение, значение которого интепретируется как истина или ложь. 174 | Условные выражения вычисляются так: сначала вычисляется предикат. Если его значением является ложь, то вычисляется предикат . Если значение также ложь, вычисляется . Этот процесс будет продолжаться до тех пор, пока не будет найден предикат, значением которого является истина, и в этом случае интерпретатор возвращает значение соответствующего выражения-следствия в качестве значения всего условного выражения. Если ни один из

ни окажется истинным, значение условного выражения не определено. 175 | Процедуру вычисления модуля числа, можно написать и так: 176 | :: 177 | 178 | (define (abs x) 179 | (cond ((< x 0) (-x)) 180 | (else x))) 181 | 182 | Else - специальный символ, который в заключительной ветви cond можно использовать на месте

. Это заставляет cond вернуть в качестве значения значение соответствующего в случае, если все предыдущие ветви были пропущены. 183 | Вот еще один способ написать процедуру вычисления модуля: 184 | :: 185 | 186 | (define (abs x) 187 | (if (< x 0) 188 | (-x) 189 | x)) 190 | 191 | Здесь употребляется особая форма if, ограниченный вид условного выражения. Общая форма if такова: 192 | :: 193 | 194 | (if <предикат> <следствие> <альтернатива>) 195 | 196 | В дополнении к элементарным предикатам вроде <, =, > существуют операции логической композиции, которые позволяют нам конструировать составные предикаты. 197 | :: 198 | 199 | (and ... ) 200 | 201 | Интерпретатор вычисляет значение выражения по одному, слева направо. Если какое-нибудь из дает ложное значение, значение всего выражения and - ложь, и остальные не вычисляются. Если все дают истинные значения, значением выражения and является значение последнего из них. 202 | :: 203 | 204 | (or ... ) 205 | (not ) 206 | 207 | Примеры: 208 | :: 209 | 210 | (and (> x 5) (< x 10)) 211 | 212 | Определение предиката, которые проверяет, что одно число больше или равно другому, как: 213 | :: 214 | 215 | (define (>= x y) 216 | (or (> x y) (= x y))) 217 | 218 | Или как: 219 | :: 220 | 221 | (define (>= x y) 222 | (not (< x y))) 223 | 224 | Упражнения 225 | ^^^^^^^^^^^^^^^^^^^^^^ 226 | 1.1 Ниже приведена последовательность выражений. Какой результат напечает интерпретатор в ответ на каждое из них? Предполагается, что выражения вводятся в том же порядке, в каком они написаны. 227 | :: 228 | 229 | 10 230 | (+ 5 3 4) 231 | (- 9 1) 232 | (/ 6 2) 233 | (+ (* 2 4) (- 4 6)) 234 | (define a 3) 235 | (define b (+ a 1)) 236 | (+ a b (* a b)) 237 | (= a b) 238 | (if (and (> b a) (< b (* a b))) 239 | b 240 | a) 241 | (cond ((= a 4) 6) 242 | ((= b 4) (+ 6 7 a)) 243 | (else 25)) 244 | (+ 2 (if (> b a) b a)) 245 | (* (cond ((> a b) a) 246 | ((< a b) b) 247 | (else -1)) 248 | (+ a 1)) 249 | 250 | Ответ: 251 | :: 252 | 253 | 10 254 | 12 255 | 8 256 | 3 257 | 6 258 | a 3 259 | b 4 260 | 19 261 | false 262 | 4 263 | 16 264 | 6 265 | 16 266 | 267 | 1.2 Переведите следующее выражение в префиксную форму (выражение лень приводить, длинная дробь) 268 | Ответ: 269 | :: 270 | (/ (+ 5 271 | 4 272 | (- 2 273 | (- 3 274 | (+ 6 4/5)))) 275 | (* 3 276 | (- 6 2) 277 | (- 2 7))) 278 | 279 | 1.3 Определите процедуру, которая принимает в качестве аргументов три числа и возвращает сумму квадратов двух больших из них 280 | Ответ: 281 | :: 282 | 283 | (define (three_num_sqrt a b c) 284 | (cond ((and (< a c) (< a b)) (+ (sqrt b) (sqrt c))) 285 | ((and (< b a) (< b c)) (+ (sqrt a) (sqrt c))) 286 | (else (+ (sqrt a) (sqrt b))))) 287 | 288 | 1.4 Заметим, что наша модель вычислений разрешает существование комбинаций, операторы которых составные выражения. С помощью этого наблюдения опишите, как работает следующая процедура: 289 | :: 290 | 291 | (define (a-plus-abs-b a b) 292 | ((if (> b 0) + -) a b)) 293 | 294 | Ответ: 295 | Процедура складывается аргумент а с модулем аргумента b. Если аргумент b процедуры положителен, то к аргументам a и b применяется оператор сложения, иначе оператор вычитания. 296 | 297 | 1.5 Бен Битобор придумал тест для проверки интерпретатора на то, с каким порядком вычислений он работает, аппликативным или нормальным. Бен определяет такие две процедуры: 298 | :: 299 | 300 | (define (p) (p)) 301 | 302 | (define (test x y) 303 | (if (= x 0) 304 | 0 305 | y)) 306 | 307 | Затем он вычисляет выражение 308 | :: 309 | 310 | (test 0 (p)) 311 | 312 | Какое поведение увидит Бен, если интерпретатор использует аппликативный порядок вычислений? Какое поведение он увидит, если интерпретатор использует нормальный порядок? Объясните ответ. 313 | Ответ: 314 | Предположим, что интерпретатор использует аппликативный порядок вычислений, т.е. сначала вычисляет операнды и оператор, а затем применяет получившуюся процедуру к получившимся аргументам. Тогда, вычисляя аргументы процедуры test получаем: 315 | :: 316 | 317 | test 0 p 318 | 319 | Применяя тело процедуры к аргументам получаем 0. 320 | 321 | Предположим, что интерпретатор использует нормальный порядок вычислений. 322 | :: 323 | 324 | (if (= 0 0) 325 | 0 326 | (p)) 327 | 328 | (if (= 0 0) 329 | 0 330 | p) 331 | 332 | 0 333 | 334 | Авторы учебника, пишут, что для данного примера нормальный и аппликативный порядки вычислений дают разные результаты. Но почему? 335 | 336 | --------------------------------------------------------------------------------