Основы функционального программирования

       

Переменные


Переменная — это символ, который используется для представления аргумента функции.

Таким образом, возвращаясь к Дж.Мак-Карти [1]:

"Можно написать "a + b, где a = 341 и b = 216". В такой ситуации не может быть недоразумений, и все согласятся, что ответ есть 557. Чтобы получить этот результат, необходимо заменить переменные фактическими значениями, и затем сложить два числа (на арифмометре, например).

Одна из причин, по которой в этом случае не возникает недоразумений, состоит в том, что "a" и "b" не есть приемлемые входы для арифмометра, и следовательно, очевидно, что они только представляют фактические аргументы. При символьных вычислениях ситуация может быть более сложной. Атом может быть как переменной, так и фактическим аргументом. К дальнейшему усложнению приводит то обстоятельство, что некоторые из аргументов могут быть переменными, вычисляемыми внутри вызова другой функции. В таких случаях интуитивный подход может подвести. Для эффективного программирования в функциональном стиле необходимо более точное понимание формализмов.

Чтобы не обескураживать читателей, следует заметить, что здесь ничего принципиально нового нет. Все, что сейчас рассматривается, может быть логически выведено из правил представления

программ в виде S-выражений или из определения универсальных функций eval/apply, и является их непосредственным следствием, возможно, не вполне очевидным.

Любой формализм для переменных сводится к лямбда-обозначению. Часть интерпретатора, которая при вычислении функций связывает переменные, называется APPLY. Когда APPLY встречает функцию, начинающуюся с LAMBDA, список переменных попарно связывается со списком аргументов и добавляется к началу а-списка. При вычислении функции могут быть обнаружены переменные. Они вычисляются поиском в а-списке. Если переменная встречается несколько раз, то используется последнее или самое новое значение. Часть интерпретатора, которая делает это, называется EVAL. Проиллюстрируем данное рассуждение на примере.
Предположим, что интерпретатор получает следующее S-выражение: ((LAMBDA (X Y) (CONS X Y)) 'A 'B)

Функция:((LAMBDA (X Y) (CONS X Y)) 'A 'B)

Аргументы: (A B)

EVAL0 через EVAL передает эти аргументы функции APPLY. (См. лек. 3). (apply #’(LAMBDA (X Y) (CONS X Y)) ‘(A B) Nil )

APPLY свяжет переменные и передаст функцию и удлиннившийся а-список EVAL для вычисления. (eval ‘(CONS X Y) ‘ ((X . A) (Y . B) Nil))

EVAL вычисляет переменные и сразу передает их консолидации, строящей из них бинарный узел. (Cons ‘A ’B) = (A . B)



EVAL вычисляет переменные и сразу передает их консолидации, строящей из них бинарный узел.

Реальный интерпретатор пропускает один шаг, требуемый формальным определением универсальных функций".

На практике сложилась традиция включать в систему функционального программирования

специальные функции, обеспечивающие иерархию контекстов, в которых переменные обладают определенным значением. Для Clisp это let и let*.

Let сопоставляет локальным переменным независимые выражения. С ее помощью можно вынести из сложного определения любые совпадающие подвыражения.

(defun UNION (x y)

(let ( (a-x (CAR x)) (d-x (CDR x)) ) ; конец списка локальных именованных значений

(COND ((NULL x) y) ((MEMBER a-x y) (UNION d-x y) ) ; использование локальных (T (CONS a-x (UNION d-x y)) ) ; значений из контекста ) ) ; завершение контекста let )

Let* — сопоставляет локальным переменным взаимосвязанные выражения. Она позволяет дозировать сложность именуемых подвыражений.

(defun member (a x) (let* ( (n-x (null x)) (a-x (car x)) (d-x (cdr x)) (e-car (eq a a-x)) ) ; список локально именованных выражений

(COND (N-X Nil) ; использование (E-CAR T) ; именованных (T (MEMBER A D-X)) ; выражений ) ) ; выход из контекста именованных выражений )

(Эквивалентность с точностью до побочного эффекта.)

Глобальные переменные можно объявить с помощью специальной функции defparameter.

(DefParameter glob '(a b c))

Значение такой переменной доступно в любом контексте, оно может быть переопределено. Возможно оттеснение одноименными локальными переменными с восстановлением при выходе из соответствующих контекстов. При выполнении кода программ:

(let ((glob 12))(print glob)) (print glob)

напечатано будет:

12 (A B C)


Содержание раздела