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

Введение в лямбда исчисление

< Лекция 6 || Лекция 7: 12345 || Лекция 8 >

Лямбда-исчисление и агенты

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

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

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

Даже в отсутствие механизма агентов методы являются формой лямбда-выражений, и вызов метода является формой бета-редукции. Но эта редукция должна планироваться статически, через вызовы, такие как f(x, y) с явно заданным методом f ОО-программирование вводит первый элемент динамизма благодаря динамическому связыванию, разрешая f иметь несколько вариантов, выбор между которыми делается при каждом вызове a.f(x, y) на основе типа объекта, присоединенного к а. Такой динамический механизм позволяет нам представить ряд примеров в виде образца "много маленьких оберток" (с цитированными ограничениями), но предлагаемый выбор ограничен множеством построенных вариантов. С агентами бета-редукция становится полностью динамической операцией, вызов a.call([x, y]) не требует от нас какого-либо знания о методе, который ассоциирован с агентом, за исключением знания сигнатуры.

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

  • В e \triangleq \lambda x: INTEGER | x + y связанная переменная x представляет аргумент, который будет задан во время бета-редукции, свободная переменная приходит из окружения, типично из охватывающего выражения.
  • В agent f(?, y) открытый аргумент будет задан во время вызова, закрытый аргумент обеспечивается при определении.

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

  • agent{C}.f — динамическая версия f в полной мере соответствующая сигнатуре оригинала.
  • Другой крайний вариант — agent f(x, y) и agent t. f(x, y), где все операнды закрыты, полностью соответствует карринг-версии, так что можно вызывать (если а агент) а.саll([]) без всяких аргументов. Это, кстати, напоминает нам разницу между математикой и программированием: математическая функция при выполнении карринга по всем аргументам превращается в константу, в то же время успешные вызовы а.саll([]) могут давать разные результаты, поскольку даже если а не изменяется, могут изменяться окружающие объекты.
  • Посередине вариант с агентом, имеющим как открытые, так и закрытые операнды, такие как agent a. f(?, x, y), подобный функции с каррингом на закрытых операндах.

В одном отношении агенты, которые мы видели до сих пор, менее общие, чем лямбда-выражения. Чтобы использовать agent а. f(?, x, y) или любой другой вариант, мы должны предположить, что функция f построена. Такое предположение для лямбда-выражения λx...|exp означало бы ограничение exp формой f(args). Теперь покажем, как для агентов можно снять это ограничение, позволяя агентам иметь произвольную форму, как это происходит для exp в лямбда-выражении.

Манифестные агенты, непосредственно определяющие функцию

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

Агенты, изучаемые до сих пор, связывались с существующими методами класса. Но иногда хочется иметь агента и не иметь метода в классе. Представьте себе, что нужно выполнить с использованием агента некоторое простое вычисление. Кажется излишним во всех случаях создавать в классе метод, который может и не отражать свойства класса. Метод может понадобиться только в одном месте, и нет смысла перегружать им класс11 Ситуация похожа на ситуацию с константами. Если константа используется многократно, то следует создавать именованную константу. Для констант однократного употребления иногда полезнее и понятнее использовать в точке ее применения манифестную константу. Аналогично - манифестный агент может непосредственно в точке вызова определить вызываемую функцию.. Манифестные агенты ( inline ) позволяют определить агент, не тревожа никакой класс.

Такая необходимость часто возникает при написании контрактов — всех видов предусловий, постусловий, инвариантов класса. Например, инвариант класса может специфицировать, что все элементы некоторого массива целых являются положительными. Мы уже знаем, как это установить, благодаря классу INTEGER_INTERVAL и оператору |..|, рассматриваемому в параграфе "Агенты и итерации" этой лекции. Мы видели, как установить требуемое условие, эквивалент выражения _s:a.lower..a.upper | a[i]>0 в исчислении предикатов:

(a.lower |..| a.upper). for_all (agent is_positive) [26]
Листинг 6.9.

Чтобы сделать это условие выполнимым, следует написать небольшую функцию для данного случая.

is_positive (n: INTEGER): BOOLEAN
    - Больше ли n нуля?
  do
    Result := (n > 0)
  ensure
    definition: Result = (n > 0)
  end
Листинг 6.10.

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

Манифестные агенты в полной мере отвечают поставленной задаче. Манифестный агент, как говорит его имя, задает объявление метода в стиле, подобном объявлению метода, но ничего более не требуется, никакие методы класса не появляются. Синтаксис непосредственно выводится из переписи нашего последнего примера. Мы, по сути, сливаем [6.10]в [6.9], получая в результате:

(a.lower \..\ a.upper). for_all
  (agent (n: INTEGER): BOOLEAN [28]
        - Больше ли n нуля?
    do
      Result := (n > 0)
    ensure
      definition: Result = ( n > 0)
    end)

Начиная со второй строчки текст совпадает с [6.10], с тем исключением, что исчезает ненужное теперь имя метода (агент манифестный).

Манифестный агент характеризуется следующим свойством: он задает анонимный метод.

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

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

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

Конструкции в других языках

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

Не все языки программирования, однако, обладают такие конструкциями. Фактически среди языков, применяемых в индустрии, только Eiffel, Smalltalk и C# имеют похожие средства (существенно отличаясь в деталях). Представляет интерес вкратце рассмотреть, какие же решения являются доступными в зависимости от языка, который, возможно, вы используете.

Применяются четыре основных подхода:

  • механизм, поддерживающий лямбда-выражения, такой как агенты;
  • методы, как аргументы других методов;
  • указатели функций;
  • образец "много маленьких оберток".
< Лекция 6 || Лекция 7: 12345 || Лекция 8 >
Надежда Александрова
Надежда Александрова

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