Объекты и полиморфизм. Ч.2: различия между версиями

Материал из wikiru.visual-prolog.com

(Новая: ==Пример. Планирование работ== Теперь попытаемся объединить кое-что из вышесказанного для разработки ...)
(нет различий)

Версия 07:44, 22 сентября 2007

Пример. Планирование работ

Теперь попытаемся объединить кое-что из вышесказанного для разработки простого, но достаточно мощного планировщика работ.

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

Планировщик будет опираться на события таймера: каждый раз, когда событие таймера запускается, планировщик просматривает работы, находящиеся в ожидании и исполняет их. В этом примере время имеет тип integer. В программе могут быть использованы несколько планировщиков, поэтому представлять каждый планировщик будет объект. Планировщик описывается интерфейсом, или типом объекта. Объявление планировщика выглядит так:

interface scheduler
domains
  job = (integer Time) procedure (i).
predicates
  registerJob : (integer Time, job Job).
predicates
  timerTick : (integer Time).
end interface scheduler

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

registerJob имеет очевидное назначение.

timerTick должен периодически вызываться, т.к. является сердцем планировщика.

Раздел implement выглядит так:

implement scheduler
  open core, priorityQueue
facts
  jobQueue : queue{integer, job}.
clauses
  new() :-
    jobQueue := empty.
clauses
  registerJob(Time, Job) :-
    jobQueue := insert(jobQueue, Time, Job).
clauses
  timerTick(Time) :-
    if
      tuple(T, Job) = tryGetLeast(jobQueue),
      T <= Time
    then
      Job(Time),
      jobQueue := deleteLeast(jobQueue),
      timerTick(Time)
    end if.
end implement scheduler

Большинство членов этого раздела очевидны. Главное состоит в том, что приоритет в виде времени используется для сохранения работ. Это удобно, т.к. самые ожидаемые работы оказываются наиболее доступными. Факт jobQueue инициализируется конструктором, а предикат registerJob вставляет работу в очередь с приоритетом. Важный момент имеет место в предикате timerTick. Он вставляет в очередь «наименьшую» работу. Если эта работа должна была стартовать сейчас или ранее, то работа запускается и удаляется из очереди, и затем рассматривается оставшаяся часть очереди. Если «наименьшая» работа не находится в состоянии ожидания, то ничего не происходит, т.е., ожидается следующий тик времени. Перед началом использования планировщика, следует обратить внимание на то, что имеются несколько причин, почему сам планировщик не содержит таймера:

  • Может оказаться важной синхронизация с внешними часами
  • В некоторых контекстах увеличение параметра Time не должно быть одинаковым
  • В некоторых контекстах увеличение параметра Time не соответствует реальному времени (например, шаги в играх, последовательность рабочих операций и т.д.)
  • В GUI приложениях может быть важным, чтобы timerTick вызывался оконным событием, т.е. поддержка приложения с одним потоком (thread).

Использование внешнего таймера делает все это возможным. Применим таймер в консольной программе, где часы реализуются простым циклом for.

class predicates
    traceJob : (integer Time).
clauses
    traceJob(Time) :-
        stdio::write("Now: ", Time, "\n").
clauses
    test() :-
        Scheduler = scheduler::new(),
        Scheduler:registerJob(500, traceJob),
        foreach Time = std::fromTo(1,1000) do
            Scheduler:timerTick(Time)
        end foreach.

Предикат traceJob печатает время. Мы создаем планировщик и регистрируем время (traceJob) для запуска Time = 500, затем запускаем часы. Можно выполнять работу, которая сама себя планирует, т.е., запускается повторно. Чтобы сделать это, работа должна иметь доступ к планировщику, поэтому сохраним ее в факте:

class predicates
  cyclicJob : (integer Time).
clauses
  cyclicJob(Time) :-
    stdio::write("Hello World!\n"),
    scheduler:registerJob(Time+50, cyclicJob).
class facts
  scheduler : scheduler := erroneous.
clauses
  test() :-
    scheduler := scheduler::new(),
    scheduler:registerJob(5, cyclicJob),
    foreach Time = std::fromTo(1,200) do
      scheduler:timerTick(Time)
    end foreach.

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

Другие новые конструкции

В это статье описываются некоторые новые особенности языка. Одновременно используются новые конструкции foreach и if-then-else, без объяснения их работы. Эти конструкции легко понять, и они не требуют особых объяснений. Однако, иногда, имеют место странные конструкции вроде "catch all" clauses и т.д.. Хотя они выглядят менее похожими на «старый» Пролог, фактически они имеют совершенно ясную прологовскую семантику, и одновременно, они делают код более ясным.

Заключение

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

Будущие расширения

Visual Prolog будет по-прежнему расширяться. Многие новые свойства уже намечены или находятся в стадии разработки. Особенно хочется отметить два новые свойства.

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

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

Ссылки