Введение в программирование на Лиспе

Рекурсивные функции и структуры данных

Разбить на страницы
Показывать лекцию целиком

Автор языка Лисп – профессор математики и философии Джон Мак-Карти, выдающийся ученый в области искусственного интеллекта. Он предложил проект языка Лисп, идеи которого возбудили не утихающие до наших дней дискуссии о сущности программирования. Сформулированная Джоном Мак-Каpти (1958) концепция символьной обработки информации восходит к идеям Чёрча и других видных математиков конца 20-ых годов предыдущего века. Выбирая лямбда-исчисление как основную модель, Мак-Карти предложил функции рассматривать как общее понятие, к которому могут быть сведены все другие понятия программирования [1].

Для работы с данным курсом можно воспользоваться комплектом GNU Clisp, на базе которого подготовлены примеры курса, выставленным на сайте http://green.iis.nsk.su/lisp

Определение 1.1

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

Способы определения правила и методов получения результата функции по заданному правилу при известных аргументах могут быть различны, например:

  • Алгоритм (поиск наибольшего общего делителя).
  • Таблица (сложение или умножение для целых чисел).
  • Процесс (взвешивание или измерение).
  • Устройство (вольтметр, термометр, часы)
  • Формализованный текст (процедура, подпрограмма, макрос и т.п.).
  • Различаются обозначения и определения соответствия между аргументами и результатами. Интуитивно понятие функции содержит концепцию времени: сначала вычисляются аргументы в порядке перечисления, затем строится значение функции - ее результат. Процессы обработки информации организуются как применение функций к их аргументам - вычисления.

    Определение 1.2

    Вычисление – процесс решения задачи, сводимой к обработке чисел, кодов или символов, рассматриваемых как модели реальных объектов.

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

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

    Определение1.3

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

    Кроме списков в языке Лисп имеются более общие структуры данных – символьные выражения (S-выражения), реализуемые как двоичные деревья, а Лисп-системы поддерживают обработку различных специальных структур данных, таких как вектора, массивы, строки, хэш-таблицы, файлы, потоки ввода-вывода и др.

    Определение 1.4

    Двоичное дерево – это ориентированный граф, из каждой вершины которого выходит не более двух дуг.

    Элементарные данные языка Лисп называются атомами. Атомы могут иметь вид имен, чисел или других объектов, неделимых базовыми средствами языка.

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

    Определение 1.5

    Функция называется рекурсивной, если ее определение прямо или косвенно (через другие функции ) содержит обращение к самой себе.

    Система программирования может быть задана как правило интерпретации или компиляции программ.

    Определение 1.6

    Система программирования – это комплекс средств и методов, используемых при подготовке и применении программ на одном или нескольких языках программирования.

    Список из функции и перечня ее аргументов называется "форма" - синоним термина "выражение". Программа – это последовательность вычисляемых форм. Рекурсия – сведение к себе – позволяет такие правила записывать достаточно лаконично и ясно. Стек обеспечивает работу с рекурсивными функциями.

    Определение 1.7

    Стек - набор данных, в котором элементы обрабатываются согласно дисциплине "Первым пришел – последним ушел." (англ. Stack - пачка, стопка)

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

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

    Определение 1.8

    Переменная – именованная часть памяти, предназначенная для многократного доступа к изменяющимся данным.

    Определение 1.9

    Константа – именованная часть памяти, предназначенная для многократного доступа к фиксированным, не изменяющимся данным.

    Типы данных в Лиспе включены в представление значений. Поэтому при вычислении они всегда известны и могут быть проверены в любой момент.

    Определение 1.10

    Тип данных – множество данных с соответствующим ему набором допустимых операций.

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

    Функционирует Лисп-система с учетом комплекта встроенных определений атомов. Программа может влиять на этот комплект и формировать специализированные версии системы.

    Термины "Ассоциативный список", "Атом", "Сборка мусора", "Свойства атома", "Список", "Символьные выражения", "S-выражения", "Форма", "Функциональное программирование" еще будут пояснены по ходу курса.

    Элегантный лаконизм рекурсии может скрывать нелегкий путь. А.П.Ершов в предисловии к книге П.Хендерсона [2] привел поучительный пример задачи о рекурсивной формуле, сводящей вычитание единицы из натурального числа к прибавлению единицы:

    {1 –1 =  0 ;  ( n +1 )  -1 = n  } ,

    не поддавшейся А.Чёрчу и решенной С.Клини лишь в 1932 году:

    Пример 1.1Запись с помощью алгоритмической нотации школьного курса информатики

    { F (x, y, z) = если (x = 1) то 0 иначе 
                           если  ((y +1) = x) то z  иначе F (x, y +1, z +1) ;
    n –1  = F (n, 0, 0) }
    
    алг F ( цел  x, y, z) арг x, y, z 
           нач 
                если  (x = 1)
                     то знач := 0
                     инес (y +1) /= x
               то знач := F (x, y +1, z +1)
           кон
    
    алг N-1 (цел N) арг N нач  знач := F (N, 0, 0)  кон

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

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

    Лисп появился как язык символьной обработки информации. К середине семидесятых годов на Лиспе решались наиболее сложные в практике программирования задачи из области дискретной и вычислительной математики, экспериментального программирования, лингвистики, химии, биологии, медицины и инженерного проектирования. На Лиспе реализована система AutoCAD - автоматизация инженерных расчетов, дизайна и комплектации изделий из доступных элементов, и Emacs – весьма популярный текстовый редактор в мире UNIX/Linux.

    Приверженцы Лиспа ценят его за элегантность, гибкость, а, главное, за способность к точному представлению программистских идей и удобство отладки. Методы программирования на Лиспе потребовали от авторов Лиспа большого числа нетрадиционных решений и соглашений, основа которых предложена и опробована Дж. Мак-Карти с его коллегами и учениками в определении этого языка (Lisp - list processing) и в первых реализациях Lisp 1.0 и Lisp 1.5. Наиболее общие из них признаны как принципы функционального программирования:

  • Унификация понятий <функция> и <значение>.

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

  • Кроме функций-констант вполне допустимы функции-переменные.

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

  • Самоприменимость.

    Первые реализации Лиспа были выполнены методом раскрутки, причем в составе системы сразу были предусмотрены и интерпретатор и компилятор. Оба эти инструмента были весьма точно описаны на самом Лиспе, причем основной объем описаний не превосходил пару страниц.

  • Интегральность ограничений на пространственно-временные характеристики.

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

  • Уточняемость решений.

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

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

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

    В настоящее время наблюдается устойчивый рост рейтинга интерпретируемых языков программирования и включение в компилируемые языки механизмов >символьной обработки и средств динамического анализа, что повышает интерес к Лиспу и функциональному программированию. Современные языки и технологии программирования унаследовали опыт реализации и применения Лиспа и других языков символьной обработки. Так, например, Java берет на вооружение идеи неполной компиляции и освобождения памяти, объектно-ориентированное программирование реализует объекты похоже на списки свойств атомов Лиспа, хэш-таблицы языка Perl созвучны по применению ассоциативным спискам Лиспа. Python обрабатывает программы с нетипизированными переменными.

    Наследие Лиспа в информатике достойно отдельного изложения. Существуют и активно применяются более трехсот диалектов Лиспа и родственных ему языков (Interlisp, muLisp, Clisp, Scheme, Ml, Cmucl, Logo, Hope, Sisal, Haskell, Miranda и т.д.) По современным меркам реализации Лиспа компактны и непритязательны к оборудованию. Существуют свободно распространяемые версии, занимающие менее Мегабайта, пригодные к применению на любом процессоре.

    В этом курсе мы сконцентрируемся на ключевой идее Лиспа - сведении понятия "программа" к взаимодействию разных категорий функций, а также на основах и методах функционального программирования. Подробно познакомимся с базисом Лиспа, проанализируем конструктивность методов программирования на Лиспе, изучим построение Лисп-системы и узнаем ее архитектурные особенности, рассмотрим методы эффективного и прикладного программирования в функциональном стиле. С математическими основами Лиспа можно ознакомиться подробнее на страницах журнала "Компьютерные инструменты в образовании", N 2-5 за 2002 год.

    Выводы:

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

  • Назовите авторов различных идей в программировании.
  • Перечислите названия языков и систем программирования, а также информационных систем, с которыми вы знакомы.
  • Опишите наиболее известные принципы программирования и приемы записи программ.
  • Какие понятия и конструкции необходимо изображать в текстах программ?
  • Страницы:

    Автор языка Лисп – профессор математики и философии Джон Мак-Карти, выдающийся ученый в области искусственного интеллекта. Он предложил проект языка Лисп, идеи которого возбудили не утихающие до наших дней дискуссии о сущности программирования. Сформулированная Джоном Мак-Каpти (1958) концепция символьной обработки информации восходит к идеям Чёрча и других видных математиков конца 20-ых годов предыдущего века. Выбирая лямбда-исчисление как основную модель, Мак-Карти предложил функции рассматривать как общее понятие, к которому могут быть сведены все другие понятия программирования [1].

    Для работы с данным курсом можно воспользоваться комплектом GNU Clisp, на базе которого подготовлены примеры курса, выставленным на сайте http://green.iis.nsk.su/lisp

    Определение 1.1

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

    Способы определения правила и методов получения результата функции по заданному правилу при известных аргументах могут быть различны, например:

  • Алгоритм (поиск наибольшего общего делителя).
  • Таблица (сложение или умножение для целых чисел).
  • Процесс (взвешивание или измерение).
  • Устройство (вольтметр, термометр, часы)
  • Формализованный текст (процедура, подпрограмма, макрос и т.п.).
  • Различаются обозначения и определения соответствия между аргументами и результатами. Интуитивно понятие функции содержит концепцию времени: сначала вычисляются аргументы в порядке перечисления, затем строится значение функции - ее результат. Процессы обработки информации организуются как применение функций к их аргументам - вычисления.

    Определение 1.2

    Вычисление – процесс решения задачи, сводимой к обработке чисел, кодов или символов, рассматриваемых как модели реальных объектов.

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

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

    Определение1.3

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

    Кроме списков в языке Лисп имеются более общие структуры данных – символьные выражения (S-выражения), реализуемые как двоичные деревья, а Лисп-системы поддерживают обработку различных специальных структур данных, таких как вектора, массивы, строки, хэш-таблицы, файлы, потоки ввода-вывода и др.

    Определение 1.4

    Двоичное дерево – это ориентированный граф, из каждой вершины которого выходит не более двух дуг.

    Элементарные данные языка Лисп называются атомами. Атомы могут иметь вид имен, чисел или других объектов, неделимых базовыми средствами языка.

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

    Определение 1.5

    Функция называется рекурсивной, если ее определение прямо или косвенно (через другие функции ) содержит обращение к самой себе.

    Система программирования может быть задана как правило интерпретации или компиляции программ.

    Определение 1.6

    Система программирования – это комплекс средств и методов, используемых при подготовке и применении программ на одном или нескольких языках программирования.

    Список из функции и перечня ее аргументов называется "форма" - синоним термина "выражение". Программа – это последовательность вычисляемых форм. Рекурсия – сведение к себе – позволяет такие правила записывать достаточно лаконично и ясно. Стек обеспечивает работу с рекурсивными функциями.

    Определение 1.7

    Стек - набор данных, в котором элементы обрабатываются согласно дисциплине "Первым пришел – последним ушел." (англ. Stack - пачка, стопка)

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

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

    Определение 1.8

    Переменная – именованная часть памяти, предназначенная для многократного доступа к изменяющимся данным.

    Определение 1.9

    Константа – именованная часть памяти, предназначенная для многократного доступа к фиксированным, не изменяющимся данным.

    Типы данных в Лиспе включены в представление значений. Поэтому при вычислении они всегда известны и могут быть проверены в любой момент.

    Определение 1.10

    Тип данных – множество данных с соответствующим ему набором допустимых операций.

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

    Функционирует Лисп-система с учетом комплекта встроенных определений атомов. Программа может влиять на этот комплект и формировать специализированные версии системы.

    Термины "Ассоциативный список", "Атом", "Сборка мусора", "Свойства атома", "Список", "Символьные выражения", "S-выражения", "Форма", "Функциональное программирование" еще будут пояснены по ходу курса.

    Элегантный лаконизм рекурсии может скрывать нелегкий путь. А.П.Ершов в предисловии к книге П.Хендерсона [2] привел поучительный пример задачи о рекурсивной формуле, сводящей вычитание единицы из натурального числа к прибавлению единицы:

    {1 –1 =  0 ;  ( n +1 )  -1 = n  } ,

    не поддавшейся А.Чёрчу и решенной С.Клини лишь в 1932 году:

    Пример 1.1Запись с помощью алгоритмической нотации школьного курса информатики

    { F (x, y, z) = если (x = 1) то 0 иначе 
                           если  ((y +1) = x) то z  иначе F (x, y +1, z +1) ;
    n –1  = F (n, 0, 0) }
    
    алг F ( цел  x, y, z) арг x, y, z 
           нач 
                если  (x = 1)
                     то знач := 0
                     инес (y +1) /= x
               то знач := F (x, y +1, z +1)
           кон
    
    алг N-1 (цел N) арг N нач  знач := F (N, 0, 0)  кон

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

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

    Лисп появился как язык символьной обработки информации. К середине семидесятых годов на Лиспе решались наиболее сложные в практике программирования задачи из области дискретной и вычислительной математики, экспериментального программирования, лингвистики, химии, биологии, медицины и инженерного проектирования. На Лиспе реализована система AutoCAD - автоматизация инженерных расчетов, дизайна и комплектации изделий из доступных элементов, и Emacs – весьма популярный текстовый редактор в мире UNIX/Linux.

    Приверженцы Лиспа ценят его за элегантность, гибкость, а, главное, за способность к точному представлению программистских идей и удобство отладки. Методы программирования на Лиспе потребовали от авторов Лиспа большого числа нетрадиционных решений и соглашений, основа которых предложена и опробована Дж. Мак-Карти с его коллегами и учениками в определении этого языка (Lisp - list processing) и в первых реализациях Lisp 1.0 и Lisp 1.5. Наиболее общие из них признаны как принципы функционального программирования:

  • Унификация понятий <функция> и <значение>.

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

  • Кроме функций-констант вполне допустимы функции-переменные.

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

  • Самоприменимость.

    Первые реализации Лиспа были выполнены методом раскрутки, причем в составе системы сразу были предусмотрены и интерпретатор и компилятор. Оба эти инструмента были весьма точно описаны на самом Лиспе, причем основной объем описаний не превосходил пару страниц.

  • Интегральность ограничений на пространственно-временные характеристики.

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

  • Уточняемость решений.

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

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

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

    В настоящее время наблюдается устойчивый рост рейтинга интерпретируемых языков программирования и включение в компилируемые языки механизмов >символьной обработки и средств динамического анализа, что повышает интерес к Лиспу и функциональному программированию. Современные языки и технологии программирования унаследовали опыт реализации и применения Лиспа и других языков символьной обработки. Так, например, Java берет на вооружение идеи неполной компиляции и освобождения памяти, объектно-ориентированное программирование реализует объекты похоже на списки свойств атомов Лиспа, хэш-таблицы языка Perl созвучны по применению ассоциативным спискам Лиспа. Python обрабатывает программы с нетипизированными переменными.

    Наследие Лиспа в информатике достойно отдельного изложения. Существуют и активно применяются более трехсот диалектов Лиспа и родственных ему языков (Interlisp, muLisp, Clisp, Scheme, Ml, Cmucl, Logo, Hope, Sisal, Haskell, Miranda и т.д.) По современным меркам реализации Лиспа компактны и непритязательны к оборудованию. Существуют свободно распространяемые версии, занимающие менее Мегабайта, пригодные к применению на любом процессоре.

    В этом курсе мы сконцентрируемся на ключевой идее Лиспа - сведении понятия "программа" к взаимодействию разных категорий функций, а также на основах и методах функционального программирования. Подробно познакомимся с базисом Лиспа, проанализируем конструктивность методов программирования на Лиспе, изучим построение Лисп-системы и узнаем ее архитектурные особенности, рассмотрим методы эффективного и прикладного программирования в функциональном стиле. С математическими основами Лиспа можно ознакомиться подробнее на страницах журнала "Компьютерные инструменты в образовании", N 2-5 за 2002 год.

    Выводы:

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

  • Назовите авторов различных идей в программировании.
  • Перечислите названия языков и систем программирования, а также информационных систем, с которыми вы знакомы.
  • Опишите наиболее известные принципы программирования и приемы записи программ.
  • Какие понятия и конструкции необходимо изображать в текстах программ?
  • Вернуться к учебному плану