1.1.1 выражения
Самый простой способ начать обучение программированию — рассмотреть несколько типичных примеров работы с интерпретатором диалекта Лиспа Scheme. Представьте, что Вы сидите за терминалом компьютера. Вы печатаете выражение (expression), а интерпретатор отвечает, выводя результат вычисления (evaluation) этого выражения.
Один из типов элементарных выражений, которые Вы можете вводить — это числа. (Говоря точнее, выражение, которое Вы печатаете, состоит из цифр, представляющих число по основанию 10.) Если Вы дадите Лиспу число
486
интерпретатор ответит Вам, напечатав1
486
Выражения, представляющие числа, могут сочетаться с выражением, представляющим элементарную процедуру (скажем, + или *), так что получается составное выражение, представляющее собой применение процедуры к этим числам. Например:
(+ 137 349)
486
( 1000 334)
66
(* 5 99)
495
(/ 10 5)
2
(+ 2.7 10)
12.7
Выражения такого рода, образуемые путем заключения списка выражений в скобки с целью обозначить применение функции к аргументам, называются комбинациями (combinations). Самый левый элемент в списке называется оператором (operator), а остальные элементы — операндами (operands). Значение комбинации вычисляется путем применения процедуры, задаваемой оператором, к аргументам (arguments), которые являются значениями операндов.
Соглашение, по которому оператор ставится слева от операндов, известно как префиксная нотация (prefix notation), и поначалу оно может сбивать с толку, поскольку существенно отличается от общепринятой математической записи. Однако у префиксной нотации есть несколько преимуществ. Одно из них состоит в том, что префиксная запись может распространяться на процедуры с произвольным количеством аргументов, как в следующих примерах:
(+ 21 35 12 7)
75
(* 25 4 12)
1200
Не возникает никакой неоднозначности, поскольку оператор всегда находится слева, а вся комбинация ограничена скобками.
Второе преимущество префиксной нотации состоит в том, что она естественным образом расширяется, позволяя комбинациям вкладываться (nest) друг в друга, то есть допускает комбинации, элементы которых сами являются комбинациями:
(+ (* 3 5) (- 10 6))
19
Не существует (в принципе) никакого предела для глубины такого вложения и общей сложности выражений, которые может вычислять интерпретатор Лиспа. Это мы, люди, путаемся даже в довольно простых выражениях, например
(+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))
а интерпретатор с готовностью вычисляет его и дает ответ 57.Мы можем облегчить себе задачу, записывая такие выражения в форме
(+ (* 3
(+ (* 2 4\)
(+ 3 5)))
(+ (- 10 7)
6))
Эти правила форматирования называются красивая печать(pretty printing). Согласно им, всякая длинная комбинация записывается так, чтобы ее операнды выравнивались вертикально. Получающиеся отступы ясно показывают структуру выражения2.
Даже работая со сложными выражениями, интерпретатор всегда ведет себя одинаковым образом: он считывает выражение с терминала, вычисляет его и печатает результат. Этот способ работы иногда называют циклом чтение-вычисление-печать(read-eval-print loop). Обратите особое внимание на то, что не нужно специально просить интерпретатор напечатать значение выражения3.
1. Здесь и далее, когда нам нужно будет подчеркнуть разницу между вводом, который набирает на терминале пользователь, и выводом, который производит компьютер, мы будем изображать последний наклонным шрифтом. ↩
2. Как правило, Лисп-системы содержат средства, которые помогают пользователям форматировать выражения. Особенно удобны две возможности: сдвигать курсор на правильную позицию для красивой печати каждый раз, когда начинается новая строка и подсвечивать нужную левую скобку каждый раз, когда печатается правая. ↩
3. Лисп следует соглашению, что у всякого выражения есть значение. Это соглашение, вместе со старой репутацией Лиспа как неэффективного языка, послужило источником остроумного замечания Алана Перлиса (парафразы из Оскара Уайльда), что «Программисты на Лиспе знают значение всего на свете, но ничему не знают цену» ↩