- •Функциональное и логическое программирование
- •Глава 1. Классификация языков и стилей программирования 3
- •Глава 2. Программирование на языКе лисп 16
- •Глава 3. Программирование на языке пролог 89
- •Глава 1. Классификация языков и стилей программирования
- •Основные парадигмы программирования
- •Процедурное (императивное, директивное) программирование.
- •Объектно-ориентированное программирование.
- •Декларативное программирование
- •Логическое программирование.
- •Функциональное программирование.
- •Классификация языков программирования
- •Функциональные языки
- •Логические языки
- •Глава 2. Программирование на языКе лисп
- •История создания языка Лисп
- •Диалекты языка Лисп
- •Лисп-машины
- •Область применения языка Лисп
- •Особенности языка Лисп Одинаковая форма данных и программ
- •Хранение данных, не зависящее от места
- •Автоматическое и динамическое управление памятью
- •Функциональная направленность
- •Динамическая проверка типов
- •Интерпретирующий и компилирующий режимы работы;
- •Пошаговое программирование
- •Единый системный и прикладной язык программирования
- •Основы языка Лисп
- •Понятие функции.
- •Quote блокирует вычисления
- •Базовые функции языка.
- •Функция car возвращает в качестве значения первый элемент списка.
- •Функция cdr - возвращает в качестве значения хвостовую часть списка, т. Е. Список, получаемый из исходного списка после удаления из него головного элемента:
- •Функция cons включает новый элемент в начало списка:
- •Предикат equal проверяет идентичность записей:
- •Предикат equalp проверяет наиболее общее логическое равенство:
- •Другие простейшие встроенные функции Лиспа. Функция null проверяет, является ли аргумент пустым списком:
- •Комбинации вызовов car и cdr позволяют получить любой элемент списка:
- •Наиболее общая функция, выделяющая n-й элемент списка (при этом индексация начинается с 0):
- •Функция last позволяет выделить последний элемент списка:
- •Функция list - создает список из элементов:
- •Функция setq – невычисляющее присваивание:
- •Функция setf - обобщенная функция присваивания:
- •Функции, обладающие побочным эффектом, называются псевдофункциями.
- •С символом можно связать именованные свойства:
- •Функция get - возвращает значение свойства, связанного с символом:
- •Псевдофункция remprop удаляет свойство и его значение:
- •Вызов интерпретатора eval вычисляет значение выражения.
- •Ввод и вывод.
- •Использование файлов.
- •Определение функций
- •Задание параметров в лямбда-списке.
- •Передача параметров и область их действия.
- •Вычисления в лисПе.
- •Предложение let создает локальную связь внутри формы:
- •Последовательные вычисления.
- •Разветвление вычислений.
- •Циклические вычисления.
- •Передача управления.
- •Другие циклические структуры.
- •Динамическое управление из другого контекста
- •Внутреннее представление списков
- •Функциональное программирование Рекурсия. Различные виды рекурсии.
- •Функции более высокого порядка.
- •Применяющие функционалы.
- •Отображающие функционалы.
- •Макросы
- •Типы данных
- •Основные типы данных:
- •Глава 3. Программирование на языке пролог
- •Общие сведения о языке Пролог. Язык Пролог как система, реализующая логический вывод в исчислении предикатов первого порядка.
- •Фразы Хорна как средство представления знаний.
- •Синтаксис языка Пролог.
- •Фразы, термы, факты, правила.
- •Константы:
- •Простейшая программа на Прологе
- •Выполнение запроса в Прологе.
- •Неудача запроса и возврат назад.
- •Декларативная и процедурная семантика Пролога.
- •Рекурсивные процедуры.
- •Списки.
- •Операторы.
- •Средства управления ходом выполнения программы. Предикат сократить (отсечение).
- •Отрицание как неудача запроса.
- •Встроенные предикаты.
- •Типы отношений.
- •Ограничения, обеспечивающие целостность отношений.
- •Свойства отношений.
- •Подходы к программированию на языке Пролог.
Декларативное программирование
Многие практически важные задачи требуют огромного количества вычислений и очевидный путь обеспечения требуемой вычислительной мощности – использование параллельных компьютеров. Но автоматическое распараллеливание последовательных программ очень редко обеспечивает достижение приемлемого уровня параллелизма. Программа на процедурном языке "переопределена". Она устанавливает определенный порядок вычислений, часто не связанный с решением задачи. Векторизующий компилятор пытается ретроспективно обнаружить параллелизм первоначально присущий задаче, но сделать это очень сложно, а часто и невозможно. В конечном счете, компилятор, который находит возможности для параллелизма, только избавляется от переопределенности присущей последовательным языкам. В связи с этим возник класс параллельных языков программирования, которые имеют явные средства для выражения параллелизма. Но программировать на таких языках гораздо труднее, чем на последовательных - человеку гораздо легче понять статические соответствия, чем осознать протекающий во времени процесс и совсем уж трудно наглядно представить совокупность одновременно протекающих процессов.
Декларативные языки хорошо подходят для программирования параллельных компьютеров. Они не требуют специальных методов описания параллелизма. Параллелизм программы определяется неявно её информационными связями. Другими словами, работает ли программа на последовательном или параллельном компьютере, программисту безразлично. Чем более декларативен язык программирования, тем более вероятно наличие неявного параллелизма и тем проще будет параллельное выполнение программы. Такое неявное использование параллелизма очень удобно для программиста, поскольку для него не возникает дополнительных трудностей сверх тех, которые присутствуют в программе для последовательного компьютера. Но сама система должна быть достаточно "разумна", чтобы найти наиболее эффективный способ выполнения программы. Это очень трудная задача, но некоторые положительные результаты уже достигнуты и можно надеяться, что в ближайшем будущем станет возможно эффективно выполнить декларативные программы на параллельных компьютерах "прозрачно" для программиста.
Ещё одно достоинство декларативных языков - пригодность для формальных рассуждений. Известно, что тестирование позволяет обнаружить ошибки, но не может гарантировать их отсутствия. Единственный способ убедиться в правильности программы - проанализировать её код. К сожалению, существующие методы формального анализа программ чрезвычайно трудоёмки. Дело в том, что широко используемые языки типа Си имеют очень сложную и запутанную семантику. Это означает, что очень трудно формально (и даже неформально) рассуждать о программах на таких языках. Много усилий было направлено на создание более простых и строгих языков, а также на совершенствование методов анализа, но результаты не обнадёживают. Применение формальных методов остаётся очень дорогостоящим и ограничивается теми областями, где надёжность программного обеспечения критична.
Декларативная программа представляет собой формальную теорию, и, следовательно, относительно легко может быть подвергнута логическому анализу. Причём анализ этот может производиться в терминах самой программы, не требуя привлечения дополнительных формализмов, таких как предикаты на состояниях. Более того, возникает возможность создания инструментальных средств, позволяющих автоматизировать процессы анализа, преобразования, синтеза программ. Появление таких средств может в корне изменить программирование.
Стоит упомянуть и свойственные декларативному программированию недостатки. Существующие формы декларативного программирования плохо справляются с временными аспектами многих задач. Для приложений, которые активно взаимодействуют с пользователями или внешними процессами декларативных средств часто оказываются недостаточно и программистам приходится применять довольно специфические методы, нарушающие декларативность программ.
Часто утверждают, что декларативные языки не подходят для параллельного программирования. При этом под "параллельным программированием" подразумевается не распараллеливание вычислений, а задача в некотором смысле противоположная - адекватно выразить в программе естественный параллелизм задачи. То есть речь идёт опять же о временных аспектах. Причина этих трудностей в том, что декларативные языки основаны на теориях по существу "статических". Нужны новые "динамические" формализмы, которые позволили бы включить в декларативные языки временные средства. Возможные пути их создания можно увидеть во "временных логиках", в алгебраических теориях процессов и даже в теории автоматов. В настоящее время эти теории недостаточно развиты для широкого практического применения, либо имеют довольно узкую область применения. Но стоит вспомнить, какой переворот в науке произвело открытие дифференциального исчисления, позволившего статически описывать непрерывные процессы. Другая проблема декларативных языков - эффективность. Как часто бывает, недостатки - продолжение достоинств. Поскольку структура этих языков далека от устройства вычислительных машин, достаточно трудно бывает оценить производительность программы. Правда техника реализации за последние десятилетия значительно возросла, но очень легко написать краткую, понятную и изящную программу, которая, тем не менее, будет совершенно неприемлема из-за низкой производительности. Создание же эффективных программ в некоторых случаях требует немалого искусства. Предлагаемые пути решения этой проблемы - использование специальных аннотаций и автоматические преобразования программ. Первые уже нашли некоторое применение, вторые - всё ещё предмет исследований.
Пока же на практике предпочитают включать в декларативные языки программирования императивные свойства. Такой подход не лишён смысла. В конце концов, не зря же существуют повелительные предложения и, если мы хотим, чтобы что-то произошло, проще всего сказать "сделай это". Но прямолинейное добавление императивных операторов часто перечёркивает многие достоинства декларативных языков. Объединение различных парадигм в рамках одного языка, требует тщательной проработки. К сожалению, долгое время создатели функциональных и логических языков недостаточно серьёзно относились к идее декларативности и в результате практические программы на Прологе или Лиспе не намного легче анализировать, чем даже программы на Си.
Но, несмотря на свои недостатки, декларативный подход стоит того, чтобы его изучить. Во-первых, область применения декларативных языков достаточно широка и продолжает расширяться. Во-вторых, в арсенале функционального и логического программирования накоплено немало средств, которые могут применяться и в традиционном процедурном программировании.
