Опубликован: 19.03.2004 | Уровень: специалист | Доступ: платный
Лекция 4:

Отображения и функционалы

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >

Безымянные функции

Определения в примерах 4.4 и 4.5 не вполне удобны по следующим причинам:

  • в определениях целевых функций DUBLE и SQWARE встречаются имена специально определенных вспомогательных функций;
  • формально эти функции независимы, значит, программист должен отвечать за их наличие при использовании целевых функций на протяжении всего жизненного цикла программы, что трудно гарантировать;
  • вероятно, имя вспомогательной функции будет использоваться только один раз - в определении целевой функции.

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

Учитывая это, было бы удобнее вспомогательные определения вкладывать непосредственно в определения целевых функций и обходиться при этом вообще без имен. Конструктор функций LAMBDA обеспечивает такой стиль построения определений. Этот конструктор любое выражение EXPR превращает в функцию с заданным списком аргументов (X1. .. XK) в форме так называемых LAMBDA-выражений:

(LAMBDA (x1 ... xK) expr)

Имени такая функций не имеет, поэтому может быть применена лишь непосредственно. DEFUN использует данный конструктор, но требует дать функциям имена.

Определение функций DUBLE и SQWARE из примеров 4.4 и 4.5 без использования имен и вспомогательных функций:

(DEFUN sqware (xl)
   (map-el #' (LAMBDA (x) (* x x)) xl))

(DEFUN duble (xl)
   (map-el #' (LAMBDA (x) (CONS x x)) xl))
4.9.

Любую систему взаимосвязанных функций можно преобразовать к одной функции, используя вызовы безымянных функций.

Композиции функционалов, фильтры, редукции

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

Декартово произведение хочется получить определением вида:

(DEFUN decart (x y)
   (map-el #' (lambda (i)
            (map-el #' (lambda (j) (list i j)) y)
      ) x) )
4.10.

Но результат вызова

(decart '(a s d) '( e r t))

дает

(((A E) (A R) (A T)) ((S E) (S R) (S T)) ((D E) (D R) (D T)))

вместо ожидаемого

((A E) (A R) (A T) (S E) (S R) (S T) (D E) (D R) (D T))

Дело в том, что функционал MAP-EL, как и MAP-COMP (пример 4.7), собирает результаты отображающей функции в общий список с помощью операции CONS так, что каждый результат функции образует отдельный элемент.

А по смыслу задачи требуется, чтобы список был одноуровневым.

Посмотрим, что получится, если вместо CONS при сборе результатов воспользоваться функцией APPEND.

Пусть дан список списков. Нужно их все сцепить в один общий список.

(DEFUN list-ap (ll)
   (COND
      (ll (append (CAR ll)
         (list-ap (CDR ll))
) ) ) )

(list-ap '((1 2)(3 (4))))   ; = (1 2 3 (4))
4.11.

Тогда по аналогии можно построить определение функционала MAP-AP:

(DEFUN map-ap (fn ll)
   (COND
      (ll (append (FUNCALL fn (CAR ll) )
         (map-ap fn (CDR ll) )
) ) ) )

(map-ap 'CDR '((1 2 3 4) (2 4 6 8) (3 6 9 12))) 
               ; = (2 3 4 4 6 8 6 9 12)

Следовательно, интересующая нас форма результата может быть получена:

(DEFUN decart(x y)
   (map-ap #'(LAMBDA(i)
         (map-el #'(LAMBDA(j)(list i j))
           y))x))
(decart '(a s d) '(e r t))
   ; = ((A E)(A R)(A T)(S E)(S R)(S T)(D E)(D R)(D T))

Сцепление результатов отображения с помощью APPEND обладает еще одним полезным свойством: при таком сцеплении исчезают вхождения пустых списков в результат. А в Лиспе пустой список используется как ложное значение, следовательно, такая схема отображения пригодна для организации фильтров. Фильтр отличается от обычного отображения тем, что окончательно собирает не все результаты, а лишь удовлетворяющие заданному предикату.

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

(DEFUN heads (xl) (map-ap
   #'(LAMBDA (x) (COND (x (CONS (CAR x) NIL)))) 
            ; временно голова размещается в список,
            ; чтобы потом списки сцепить
   xl
) )
(heads '((1 2) () (3 4) () (5 6)) ) 
            ; = (1 3 5)
4.12.

Рассмотрим еще один типичный вариант применения функционалов. Представим, что нас интересуют некие интегральные характеристики результатов, полученных при отображении, например, сумма полученных чисел, наименьшее или наибольшее из них и т.п. В таком случае говорят о свертке результата или его редукции. Редукция заключается в сведении множества элементов к одному элементу, в вычислении которого задействованы все элементы множества.

Подсчитать сумму элементов заданного списка.

(DEFUN sum-el ( xl)
   (COND ((null xl) 0)
      (xl (+ (CAR xl)
         (sum-el (CDR xl) )
) ) ) )

(sum-el '(1 2 3 4) )   ; = 10
4.13.

Перестроим такое определение, чтобы вместо "+" можно было использовать произвольную бинарную функцию:

(DEFUN red-el (fn xl)
   (COND ((null xl) 0)
      (xl (FUNCALL fn (CAR xl)
         (red-el fn (CDR xl) ) 
) ) ) )
(red-el '+ '(1 2 3 4) )   ; = 10

В какой-то мере MAP-AP ведет себя как свертка - она сцепляет результаты без сохранения границ между ними.

Такие формулы удобны при моделировании множеств, графов и металингвистических формул, а к их обработке сводится широкий класс задач не только в информатике.

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Дарья Федотова
Дарья Федотова
Сергей Березовский
Сергей Березовский

В рамках проф. переподготовки по программе "Программирование"

Есть курсы, которые я уже прошел. Но войдя в курс я вижу, что они не зачтены (Язык Ассемблера и архитектура ЭВМ, Программирование на С++ для профессионалов). Это как?

Алина Ленкова
Алина Ленкова
Россия, Ставрополь, СФ МГУПИ, 2014
Валерий Ромашов
Валерий Ромашов
Россия