Опубликован: 04.04.2012 | Доступ: свободный | Студентов: 1989 / 61 | Оценка: 4.60 / 4.40 | Длительность: 13:49:00
Лекция 6:

Агенты как функциональный тип данных

< Лекция 5 || Лекция 6: 1234 || Лекция 7 >

Агенты для численного интегрирования

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

Стандартная техника численного вычисления интеграла от вещественной функции f на конечном интервале a...b состоит, как упоминалось в предыдущей лекции, в аппроксимации точного значения интеграла ФОРМУЛА!!!! конечной суммой площадей многих маленьких прямоугольников.

Если все эти прямоугольники имеют ширину step, то прямоугольник с координатами по оси абсцисс (x, x + step) имеет площадь step * f(x). Аппроксимация интеграла на заданном интервале является суммой

Численное вычисление интеграла

Рис. 5.6. Численное вычисление интеграла
\sum\limits_{i=0}^{n-1} f(a+i*step)*step

всех площадей для всех x, таких, что a < x <. b. Число прямоугольников примерно равно (b — a)/ step

Агенты дают нам возможность написать функцию вычисления интеграла integral не только для конкретной подынтегральной функции f — например, функции косинус (cosine), — но и для любой применимой функции.

Вот пример реализации integral:

integral (f : FUNCTION [ANY,  TUPLE [REAL], REAL]; a, b : REAL): REAL
    - Aппроксимация вычисления интеграла от функции f на интервале a..b
  local
    x: REAL ; i: INTEGER
  do
    from x := a until x >= b loop
      Result := Result + f.item
      i := i + 1; x := a + i * step
    end
  end 

Объявление f указывает, что это функция с одним аргументом типа REAL, возвращающая значение типа REAL. Для вычисления значения функции в точке используем f.item ([x]). Ранее функция item для агентов уже была определена — она вызывает call и возвращает результат этого вызова. Эта функция ожидает кортеж в качестве аргумента, здесь ей передается манифестный кортеж.

Обратите внимание на вычисление заново х на каждом шаге вместо последовательного добавления шага. Это делается сознательно во избежание накопления ошибки.

Функция integral является частью класса INTEGRATOR, который описывает объекты, отвечающие за интегрирование математических функций. Если your_integratorобъект этого типа, то можно получить интеграл от функции f на интервале a..b как значение

your_integrator. integral(agent f, a, b)

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

Открытые операнды

Иногда при использовании агентов необходимо больше гибкости, поскольку нужно помимо аргументов, передаваемых ассоциированному методу при вызове, задать некоторые дополнительные аргументы, но сделать это надо один раз и на все время определения агента. Мы будем говорить о "закрытых" и "открытых" аргументах.

Открытые аргументы

В качестве простого примера рассмотрим вариант последней схемы, где мы по-прежнему хотим вычислить интеграл от функции, но у функции есть дополнительные аргументы:

\int\limits_a^b g(u,x,v)dx

Переменные u и v остаются константами во время интегрирования — только х, как и ранее, учитывается при интегрировании. Но значения переменных нужны для вычисления значения функции. Конечно, можно по-прежнему использовать предыдущее решение:

your_integrator.integral (agent g_extended, a, b
Листинг 5.13.

Необходимо определить функцию:

g_extended (x: REAL): REAL
    — Функция та же, что и g, с первым и третьим аргументами, равными u и v
  do
    Result := g ( u, x, v)
  end

Предполагается, что u и v являются атрибутами класса INTEGRATOR. Это работает, но писать такие функции утомительно, и особенно неприятно, если u и v являются локальными переменными или формальными аргументами.

Функции, такие как g_extended, просто обертка, чья единственная цель состоит в заморозке некоторых аргументов функции, превращая ее в функцию только оставшихся аргументов. Делать это приходится часто, поэтому стоит ввести подходящее понятие. Тот же эффект, что и в [5.13], можно получить без введения обертывающей функции, а используя выражение:

your_integrator.integral (agent g (u,  ?, v), a, b)

Агентное выражение agent g(u, ?, v) обозначает функцию с одним аргументом, полученную из функции с тремя аргументами заморозкой первого и третьего аргумента значениями u и v соответственно. Истинным аргументом остается только аргумент во второй позиции, помеченный знаком вопроса.

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

В частности, это означает, что наша первая нотация агента — agent f — является простым сокращением записи

agent f(?, ??, ...)

Здесь, у функции открыты все аргументы. В этом случае проще использовать краткую форму: agent f.

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

increase_sum_by_power (?, n: INTEGER)
    - Добавить к sum значение m в степени n.
  do sum := sum + m^n ensure added: sum = old sum +m^n end

Теперь sum типа REAL, так как значение этого типа возвращается после возведения в степень. После присваивания sum:= 0.0 можно получить сумму квадратов всех элементов списка il, вызвав:

il.do_all (agent increase_sum_by_power (?, 2)

Обобщая:

Определение: открытый и закрытый операнд
Операнд агента закрыт, если он специфицирован в момент определения агента. Операнд открыт, если он задается только внутри вызовов агента. В определении агента такой операнд помечается знаком вопроса - "?".

Терминологическое напоминание. "Определением агента" является выражение, его специфицирующее, такое как agent f(а, ?). Вызовом агента является оператор, вызывающий ассоциированный с агентом метод во время выполнения.

Открытые цели

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

Рассмотрим снова прежний пример с маршрутами и остановками:

 your_route.do_all (agent print_stop_name)
Листинг 5.14.

Вызов идентичен [5.5] за тем исключением, что в данном случае нет необходимости в процедуре do_at_every_stop, мы можем непосредственно вызывать do_all, так как в Traffic-класс ROUTE является фактически потомком LINEAR[STOP]. Пример предполагает процедуру print_stop_name с сигнатурой

print_stop_name (s: STOP)

Появляющийся в классе C agent print_stop_name имеет тип PROCEDURE[C, TUPLE[STOP]], соответствуя типу формального аргумента do_all.

Процедура print_stop_name на экземпляры STOP смотрит извне — она не принадлежит этому классу, но имеет аргумент типа STOP. Этот аргумент и будет тем самым открытым аргументом, так как запись [5.14] в реальности является сокращением для

your_route.do_all (agent print_stop_name (?))
Листинг 5.15.

Всякий раз, когда в do_all вызывается метод call для агента, мы знаем, где это происходит: action. call[ item] для каждого item, представляющего STOP в маршруте. Эффект от этого вызова тот же, что и для прямого вызова ассоциированного метода:

print_stop_name (your_route.item)
Листинг 5.16.

Здесь подсветка в [5.15] и [5.16] показывает, что передается в качестве аргумента итерируемому действию. Схема итерации [5.14], основанная на агентах, эквивалентна циклу, явно использующему your_route, инициализируя итерацию через your_route.start, продвигаясь по структуре your_route.forth, и выполняя вызов [5.16] на каждом шаге.

Подобно любому вызову в ОО-программировании, этот вызов имеет цель, но здесь цель задана неявно — текущий объект. Цель всегда можно сделать явной, записав вызов как Current.print_stop_name (your_route.item).

Предположим теперь, что мы рассматриваем не внешний, а внутренний метод класса STOP. Например, пусть в классе STOP существует метод close, отмечающий закрытые станции. Если мы хотим закрыть всю линию, то должны выполнить close для всех остановок. Но у метода close нет аргументов, он вызывается целью и применяется к ней; его типичный вызов:

some_stop.close

Так что действие, которое следует итерировать, теперь выглядит не как в [16], а так:

your_route.item.close

В данном случае целью метода, а не его аргументом, является то, что должно быть открытым в аргументе do_all и что должно заменить выражение agentprint_stop_name (?) в [5.15] (или в краткой форме [5.14]).

Первое, что приходит в голову, — это написать нечто подобное ? close. Но это не работает, так как не задан тип цели, ведь многие классы могут иметь компонент close.

Мы должны задать тип цели и правильной формой для нашего примера является:

your_route.do_all (agent {STOP}.close)
Листинг 5.17.

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

Для открытых аргументов применяется простая запись с использованием знака "?", поскольку тип аргумента можно выяснить из известной сигнатуры метода, но можно применять и запись с явным указанием типа TYPE. Это может быть полезно, если указывается тип, отличный от типа сигнатуры, но, естественно, согласованный с ним.

Все комбинации открытых и закрытых операндов являются правильными. Предположим, что f и g — методы класса C ; f имеет аргументы, а g — без аргументов. Тогда возможно:

  • все закрыто: agentf ( x, y, z ), agentg ;
  • цель закрыта, все аргументы, если есть, открыты: agentf ( ?, ?, ? ), agentg (возможна краткая форма записи в этом случае - agentf );
  • цель закрыта, некоторые аргументы открыты, некоторые — закрыты: agentf ( ?, y, ? );
  • цель открыта, некоторые аргументы открыты, некоторые — закрыты: agent{C}f ( ?, y, ? );
  • все открыто: agent{C} ;

Эти механизмы позволяют нам иметь единое множество итераторов — do_all, do_if и другие в LINEAR и его потомках. Без этого пришлось бы иметь два множества: одно для целей, другое — для аргументов.

< Лекция 5 || Лекция 6: 1234 || Лекция 7 >
Надежда Александрова
Надежда Александрова

Уточните пожалуйста, какие документы для этого необходимо предоставить с моей стороны. Курс "Объектно-ориентированное программирование и программная инженения".