В статье рассматривается ряд мифов относительно языка Лисп.
Автор:
Написал: artish   Дата: 2008-09-08 21:53
Комментарии: (1)   Рейтинг:
Пользовательская оценка (от 1 до 10): 2.00   
Проголосовавших: 3 с 2010-02-13 02:37
Лисп-это якобы язык программирования

Лисп - это на самом деле не язык, а семейство языков программирования. Шутка. Конечно же, тут нет никакого мифа, просто нужно определиться в терминах. Далее, говоря "Лисп", мы будем говорить про Common Lisp. Common Lisp - это один из стандартов (на самом деле, у него есть еще и две версии), и имеется около десяти более или менее полных реализаций этого стандарта, каждая из которых, как правило, работает на нескольких различных платформах. Кроме этого, есть еще другие стандарты - Scheme, ISLisp, а также куча практически используемых диалектов, не подходящих под стандарты. Все, кроме Common Lisp, мы будем называть "диалектами Лиспа", а все вместе - "семейством Лиспов".

Лисп-это якобы язык, который использовался только в древности и только для решения задач "искусственного интеллекта"

В действительности, семейство Лиспов включает в себя универсальные языки программирования, к которым относится и Common Lisp.

Специальный диалект Лисп используется в AutoCad (www.autodesk.com) в качестве языка расширений для пользователя.

На Common Lisp написан конструктор электронных магазинов Yahoo! Store (store.yahoo.com) и веб-сервер cl-http (http://www.ai.mit.edu/projects/iiip/doc/cl-http/home-page.html).

Если вы установите Red Hat Linux 7.2 в конфигурации для рабочей станции, со стандартным набором пакетов, вы обнаружите:

- редактор Emacs, написанный на Emacs-Lisp, диалекте Лисп. Это - один из двух лучших редакторов для программистов (другим является vim). Он "понимает" синтаксис всех популярных языков программирования, умеет подсвечивать синтаксис и автоматически выставлять отступы, в нем можно отлаживать программы, получать почту, работать с файлами, читать документацию и т.п. Вся эта функциональность реализована в виде относительно небольшого ядра, написанного на C и реализующего Лисп-систему и основные объекты редактора, а также порядка 19МБ исходного текста на Лисп (размер приведен для Windows-версии 19.34). Пользователь, естественно, может добавлять свой код, модифицирующий поведение Emacs.

- устанавливаемый по умолчанию для графической оболочки GNOME менеджер окон Sawfish основан на диалекте Лиспа. На нем же пишутся и расширения для Sawfish.

- электронная таблица Gnumeric написана с использованием диалекта Лисп.

- приложение GIMP (GNU's Image Manipulation Program - редактор изображений) использует диалект Лисп для написания сценариев.

Примитивный диалект Лисп используется в качестве языка сценариев музыкального редактора Cakewalk.

Как ни удивительно, Лисп используется даже программистами на Perl, хотя, казалось бы, Perl собрал в себе все мыслимые и немыслимые идеи из программирования. В исходных текстах какой-то из Perl библиотек я увидел, что простейший интерпретатор Лиспа использовался для описания имен и параметров библиотечных функций. Впрочем, этот интерпретатор работает только в процессе сборки библиотеки. Но, если программист на Perl предпочел использовать для этих целей не сам Perl, это говорит о многом!

В качестве языка текстового представления данных подобие списочного формата Лиспа используется в инициализационных файлах сервера Oracle и в файлах моделей Rational Rose.

Можно заметить, что большинство примеров использования относятся не к Common Lisp. Это, отчасти, вызвано историческими причинами, отчасти - алчностью производителей коммерческих реализаций, а отчасти - тем, что Common Lisp - большой язык для "больших задач". Из перечисленных достаточно большими являются Autocad и Emacs. Сейчас обсуждается возможность перевода Emacs под Common Lisp, а почему AutoCad написан не на нем, я не знаю.

Лисп-это якобы язык функционального программирования

Это заблуждение связано с традицией преподавания в наших ВУЗах, где Лисп изучается в связи с функциональным программированием. Лисп не ограничен идеологией функционального программирования. Грубо говоря, на нем можно написать все то же самое, что на Бейсике или C++.

Лисп-это якобы интерпретатор

Большинство реализаций Лисп включают в себя и интерпретатор, и компилятор, причем они интегрированы. Компиляция может производится как на уровне файла исходного текста (и порождается на выходе тоже файл) или на уровне отдельной функции. В работающем приложении могут свободно смешиваться интерпретируемые и компилируемые функции. В любой момент можно переопределить любую определенную ранее функцию или добавить новую. Компиляция может производиться в байт-код или в машинный код, в зависимости от реализации. В некоторых реализациях (например, CMU Common Lisp) есть по два компилятора - и в байт-код, и в машинный код. Первый компилятор можно использовать для оптимизации размера, а второй - для оптимизации скорости.

Якобы Лисп работает медленно

В качестве основных причин этому называют интерпретацию и сборку мусора. С интерпретацией мы уже разобрались. А сборка мусора применяется в таких популярных языках, как Perl и Java. Прогресс привел к тому, что сборка мусора работает достаточно быстро. Отметим, что механизм подсчета ссылок, сопоставимый по быстродействию с механизмом сборки мусора, используется в модных ныне стандартах компонентно-ориентированного программирования (COM, CORBA), так что производительность Лисп не хуже, чем при использовании этих стандартов. Некоторые приверженцы Лиспа утверждают, что лучшие реализации Common Lisp по быстродействию на сложных логических задачах превосходят C++, а на числовых задачах - Фортран.

В Лиспе якобы нет типов данных

Любой ОБЪЕКТ в Лиспе имеет тип данных. Типы образуют иерархию. Например, 4.1 является числом, т.е.

(typep 4.1 'number)

возвращает t.

В то же время

(typep 4.1 'integer)

вернет nil.

Кроме того, можно получить тип значения:

> (type-of 4.1) ; Запущено на LispWorks 4.2.0 for Windows.

DOUBLE-FLOAT

А также, узнать соотношение типов между собой

> (subtypep 'double-float 'float)

T

T

Это означает, что double-float является подтипом типа double. К слову, функция subtypep возвращает два значения. Второе значение в данном случае говорит, хранится ли в системе типов информация, достаточная для сравнения типов, то есть, отображает статус успеха или неуспеха сравнения. Вообще, в Common Lisp функции могут возвращать не одно, а несколько значений. Имеются конструкции языка, позволяющие записывать возвращаемые значения в несколько переменных. В таких языках, как C, для возврата более чем одного значения приходится пользоваться параметрами, передаваемыми по ссылке или по указателю.

В то же время, для ПЕРЕМЕННОЙ указание типа данных, вообще говоря, необязательно. Это можно рассматривать и как преимущество, и как недостаток. Например, для функции max, которую в Лиспе можно для двух аргументов определить как

(defun my-max (x y) (if (> x y) x y)))

и это можно расценивать как благо. Ведь, к примеру, в C++ так определить максимум нельзя. Нужно использовать перегрузку (overloading) либо пользоваться макросами. В случае определения в виде макроса, неправильный результат даст вычисление

MAX(i++,j++), поскольку i и j будут увеличены дважды. При использовании overloading придется написать множество версий сравнения.

Однако, с точки зрения надежности и эффективности можно расценивать использование переменных без типов как недостаток. Во-первых, все-таки для сравнения целых чисел нужен другой алгоритм, чем для сравнения чисел с плавающей точкой. Поскольку в момент компиляции неизвестно, что мы сравниваем, выбор кода для сравнения будет происходить на этапе выполнения, что влечет за собой определенные издержки. Кроме того, если мы начнем сравнивать не-числа, проблема выявится только на этапе выполнения. Для борьбы с этими проблемами в Лисп имеется механизм объявлений (declarations). Если мы напишем

(defun my-max (x y) (declare (number x y)) (max x y)),

то это означает, что x и y должны быть числами. Это может приводить к следующим последствиям:

- выбор кода для сравнения чисел будет выбираться в момент компиляции.

- при компиляции вызова (my-max 4 'hello) произойдет ошибка компиляции.

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

Все данные в Лисп якобы нужно представлять в виде списков и символов

Помимо списков и символов, в Лисп есть отдельные типы данных для строк, массивов, хеш-таблиц, записей (структур), потоков ввода-вывода. Кроме того, стандарт Common Lisp включает в себя систему объектно-ориентированного программирования CLOS, далеко превосходящую по выразительным возможностям систему программирования C++. Для доказательства этого превосходства достаточно отметить, что есть возможность выбирать вызываемое тело метода, руководствуясь типами нескольких параметров, а не одного, как в C++. Например, пусть у нас определены классы screen и paper, а также классы sentence и picture. Тогда для функции draw можно определить четыре метода

(defmethod draw ((x sentence) (y screen)) (message-box x y)) ; для отображения сообщения на экране

(defmethod draw ((x sentence) (y paper)) (write x y)) ; нужно просто написать слова на бумаге

(defmethod draw ((x picture) (y screen)) (show-picture-in-a-box x y)) ; показываем картинку на экране

(defmethod draw ((x picture) (y screen)) (paint-picture-on-a-paper x y)) ; рисуем картинку на бумаге.

При вызове

>(draw my-data my-device)

выбор одного из четырех определенных методов будет динамически осуществляться в момент вызова. Это невозможно в C++, где пришлось бы делать draw либо методом общего предка объектов screen и paper, либо методом общего предка sentence и picture, а выбор кода по типу второго аргумента осуществлять с помощью анализа этого типа в конструкции if.

 

Синтаксис Лисп якобы неудобен и сложен для изучения

Конечно, удобство - это вопрос вкуса. Там, где в других языках пишется someFunction()+3, в Лиспе нужно написать (+ (some-function) 3). На первый взгляд это кажется неудобным. Но за это программист на Лисп получает массу преимушеств:

- отсутствие правил приоритета. В C нужно помнить достаточно большую таблицу правил приоритета операций, которые определены не очень логично. Например, легко ошибиться, написав (i==j || k==l) вместо ((i==j) || (k==l)), а в Лисп такая ошибка просто невозможна, потому что нужно будет написать (or (= i j) (= k l)). Так что сложность обучения в данном случае получается меньше, чем в языках, ориентированных на стандартную инфиксную форму записи параметров.

- отсутствие различия между функцией, операцией и оператором. В обычных языках есть отдельные понятия функции и оператора, что, как минимум, усложняет компилятор. Кроме того, оператор, как правило, не может вернуть значения. Например, оператор if в C не возвращает значения, и, чтобы это обойти, была придумана операция ( ? : ). Было бы удобно, если бы оператор switch в C мог также возвращать значение, которое можно присвоить переменной после завершения его выполнения. В Паскале и Бейсике даже присваивание является оператором и не возвращает значения, поэтому нельзя написать a=b=с. В Лиспе в этом плане все гораздо удобнее. if возвращает значение. Одну и ту же конструкцию if можно использовать для ветвления алгоритма и для выбора значения по условию. Также возвращает значение и cond (аналог switch), и setf (аналог присваивания). Да и вообще, в Лиспе значение возвращается всегда, где это имеет смысл.

- расширенный набор символов для формирования идентификаторов. В Лиспе допустимы такие идентификаторы: PERSON-SALARY, K&R, *, C++, %PATH%. Это возможно благодаря тому, что в качестве разделителей используются только круглые скобки и пробелы. Нельзя все же не отметить, что использование только круглых скобок создает определенные неудобства, поскольку в других языках для выделения структур языка используются круглые, квадратные и фигурные скобки. Однако, это легко преодолеть, т.к. в Лисп можно переопределять поведение парсера. Можно произвольно задать смысл квадратных и фигурных скобок.

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

Для того, чтобы понять, как это просто, приведем пример. Допустим, в программе нам часто нужно выполнять одну из трех ветвей алгоритма в зависимости от значения критерия - 1,2 или 3. Это в Common Lisp обычно делается так:

(case критерий (1 оператор1) (2 оператор2) (3 оператор3)).

Поскольку 1, 2 и 3 в этой конструкции повторяются часто и не несут никакой полезной информации для читателя, было бы удобно ввести конструкцию case-1..3, позволяющую писать более кратко:

(case-1..3 критерий оператор1 оператор2 оператор3)

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

(case критерий (1 оператор1) (2 оператор2) (3 оператор3) (t (error "case-1..3 error")).

Используя расширяемость Лиспа, очень легко определить сокращенный вариант, который расширяется в полный перед компиляцией:

> (defmacro case-1..3 (x o1 o2 o3)

   `(case ,x (1 ,o1) (2 ,o2) (3 ,o3) (t (error "case-1..3 error")))

CASE-3

Теперь Лисп-система может выполнить, например, такую команду:

> (case-1..3 2 (print "Hello") 'ura (error "Нельзя 3"))

URA

Эта возможность кажется увлекательной, но с помощью макросов можно сделать гораздо больше, и об этом мы напишем в ближайшее время. Например, ничего не стоит определить подобную конструкцию case-1..n, которая работает не для трех, а для произвольного числа n ветвей. На самом деле, простота расширения языка непосредственно связана с простотой его синтаксиса. Именно в простоте синтаксиса и состоит, на наш взгляд, одно из основных преимуществ Лиспа.



Онлайн :

0 пользователь(ей), 6 гость(ей) :




Реклама на сайте: