Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Lisp: Слезы радости, часть 4

Оригинал: "Lisp: Tears of Joy, Part 4 "
Автор: Vivek Shangari
Дата публикации: September 1, 2011
Перевод: Н.Ромоданов
Дата перевода: Август 2012 г.
Первую статью серии читайте здесь.
Предыдущую статью серии читайте здесь.

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

Есть песня "Бог живет на Земле" (God Lives on Terra; исполнитель - Julia Ecklar), которую можно было бы отнести к фольклору программистов, и в ней есть строки, в которых код на Lisp-е приравнивается к божественному творению. Мне бы хотелось думать, что именно эти строки делят всех вас, читающих эту статью, на три категории — на тех, кто уже влюбился в Lisp, на тех, кто до сих пор не понимает, что все - суета, и на тех, кого нужно еще подтолкнуть в правильном направлении (в правильном, т. е. в сторону любителей языка Lisp).

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

Lisp медленный из-за того, что это - интерпретатор

Common Lisp не является интерпретируемым языком. На самом деле даже нет разумного определения понятия "интерпретируемый язык". Мне кажется, что в качестве разумных определений "интерпретируемого языка" можно взять только следующие два определения:

  1. Язык, который может быть реализован с помощью интерпретатора.
  2. Язык, который должен быть реализован с помощью интерпретатора.

В случае первого определения все языки являются интерпретируемыми. Во втором случае ни один из языков не является интерпретируемым.

Иногда мы смешиваем понятия интерпретируемости и интерактивности. Мы склонны думать, что всякий раз, когда есть интерактивный цикл, например, как цикл чтение-выполнение-выдача результата, имеющийся в Lisp-е, то также должен быть и интерпретатор. Это неправда. Часть, связанную с выполнением, можно реализовывать с помощью компилятора.

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

Кроме того, обычным пользователям свойственно преувеличивать важность скорости. Есть целый ряд приложений, которые очень медленные, например, Perl, Shell, Tcl/Tk, Awk, и т.д. Неверно, что всегда нужна максимальная скорость.

Lisp не используется в промышленности

… или "я не видел языка Lisp в рекламе о найме на работу".

Lisp используется. Несколько крупных коммерческих программных пакетов написаны на языке Common Lisp или некотором ином варианте языка Lisp. На каком языке написано коммерческое программное обеспечение, разобраться трудно (поэтому пользователю об этом беспокоиться не нужно), но есть некоторые пакеты, о которых это хорошо известно. Interleaf, система документирования, написана на языке Lisp. То же самое можно сказать и о AutoCAD, системе автоматизированного проектирования. Оба они являются основными приложениями в своих областях. Между тем некоммерческая система Emacs, которая также является одной из важных, также написана на Lisp-е.

Но даже если бы Common Lisp вообще не использовался в промышленности, это не следовало бы рассматривать как аргумент. Что касается языков программирования, инструментальных средств и методов, то уровень их сложности в индустрии программного обеспечения является сравнительно низким. В университетах должны обучать передовым языкам, инструментальным средствам и методам в надежде на то, что однажды ими станут пользоваться в промышленности, а не учить то, чем пользуются сегодня . Тем, кто хочет, в частности, изучать инструментальные средства, востребованные на данный момент, следует бросить университет и пройти более конкретные программы обучения.

Lisp был и остается одним из доминирующих языков программирования искусственного интеллекта (Artificial Intelligence - AI), но не следует его рассматривать как язык, который является эксклюзивным в мире AI. Сообщество AI приняло этот язык в 1980-х годах, поскольку он позволял быстро разрабатывать программы, что невозможно было делать на других основных языках того времени, например, на С.

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

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

Рекурсия или итерация?

В отличие от некоторых других функциональных языков, при программировании на языке Lisp можно использовать как рекурсивный, так и итеративный стили программирования. В качестве примера рассмотрим создание функции, которая вычисляет факториал положительного целого числа n. Факториал, который записывается как n!, является произведением всех целых чисел от 1 до n; т.е.. n! = n×(n-1)×(n-2)×…×2×1. Функция, вычисляющая это число, может быть написана в рекурсивном стиле следующим образом:

(defun fac(n)
  (if (= n 0) 1
     (* n (fac (- n 1)))))

Определение функции задается следующим образом. При n равном нулю возвращается значение 1, в противном случае возвращается произведение числа n и факториала n-1. Эта же функция может быть записана в итеративном стиле следующим образом:

(defun fac(n)
  (let ((result 1))
    (dotimes (i n)
      (setq result (* result (+ i 1))))
   result))

В этой функции используется команда let, которая создает локальную переменную result с начальным значением, равным 1. Затем выполняется n итераций главного цикла, в котором каждый раз значение этой переменной умножается на значение переменной счетчика цикла (к значению этого счетчика нужно добавить единицу, поскольку команда dotimes ведет отсчет от нуля). Наконец, значение переменной result возвращается в качестве результата работы функции.

Объектная ориентированность

Как и многие другие современные языки программирования, Common Lisp является объектно-ориентированным. На самом деле, это был первый объектно-ориентированный язык стандарта ANSI, включающий в себя CLOS (Common Lisp Object System — систему объектов языка Common Lisp). CLOS предоставляет набор функций, которые позволяют определять классы, иерархию их наследования и связанные с этим классом методы.

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

(defclass shape ()
   (x y))
 
(defclass rectangle (shape)
   ((width :initarg :width)
   (height :initarg :height)))
 
(defclass circle (shape)
   ((radius :initarg :radius)))
 
(defmethod area ((obj circle))
   (let ((r (slot-value obj 'radius)))
      (* pi r r)))
 
(defmethod area ((obj rectangle))
   (* (slot-value obj 'width)
      (slot-value obj 'height)))

Используя эти определения, прямоугольник (экземпляр прямоугольника) можно создать при помощи функции make-instance, с шириной, равной 3, и высотой, равной 4:

> (setq my-rect (make-instance 'rectangle :width 3 :height 4))
#<RECTANGLE @ #x8aee2f2>

Его площадь можно вычислить с помощью метода area (площадь):

> (area my-rect)
12

Ниже я попытаюсь в моем глубоко продуманном стиле (я надеюсь, что я не услышу усмешки) объяснить некоторые темы, которые будущим почитателям Lisp-а будет трудно усваивать с помощью книг.

Функция quote

Lisp вычисляет все. Иногда вы не хотите, чтобы Lisp это делал; в ситуации, подобной следующей, для переписывания обычных правил вычисления, используемых в Lisp, применяется функция quote. Эта функция quote является специальным оператором. У него свои собственные правила вычислений, заключающиеся в том, что он ничего не вычисляет. Она получает один аргумент и возвращает его без всяких преобразований.

> (quote (+ 7 2)) (+ 7 2)

Может показаться, что это практически бесполезная функции, поскольку она ничего не делает. Но, необходимость отмены вычисления чего-либо в Lisp-е возникает настолько часто, что для этой функции предусмотрено сокращенное обозначение. В Common Lisp для сокращения quote предопределен символ ' (одинарная кавычка или апостроф). Будьте аккуратны с тем, чтобы вместо quote не подставить ` (символ обратной кавычки). Символ ` (который программисты Common Lisp называют обратной кавычкой, а программисты Scheme - квазикавычкой) имеет другую интерпретацию, о чем я расскажу в следующих статьях.

> ('(the list (+ 2 3 5) sums 3 elements))
(THE LIST (+ 2 3 5) SUMS 3 ELEMENTS)

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

> (list '(+ 0 1 1 2 3 5)  (+ 0 1 1 2 3 5))
((+ 0 1 1 2 3 5) 12)

Атом, список и предикаты

То, что находится в списке, но само не является списком, например, слова и цифры (до сих пор использовавшиеся в наших примерах), называются атомами. Атомы отделены друг от друга пробелами или скобками. Вы можете использовать термин атом для обозначения практически любого объекта Lisp, который не рассматривается как состоящий из частей. Атомы, такие как 2,718 и 42, называются числовыми атомами или просто числами.

Атомы, такие как foo, bar и +, называются символьными атомами или просто символами. Как атомы, так и списки называются символьными выражениям, или более кратко, выражениями. Символьные выражения также часто называют s-выражениями, где s - сокращение от symbolic (символьный), с тем, чтобы их отличать от m-выражений, где m — сокращение от meta. Символы иногда называют литеральными атомами.

Вы можете проверить, является ли s-выражение атомом или нет, при помощи предикатов, те. таких функций, которые возвращают значение true (истина) или false (ложь) при проверке значения на определенное свойство. В Lisp значение false указывается как NIL, а true часто обозначается специальным символом T, но все, что отличается от NIL, рассматривается как значение true. Аналогичным образом, с помощью функции listp определяется, будет ли нечто списком.

> (atom '(functional)
T
> (atom '(a b c))
T
> (listp '(a b c))
T
> (listp 2.718)
NIL

Функция setq

В большинстве языков программирования есть специальное обозначение для операции присваивания. В Lisp такого нет. Вместо этого Lisp использует свою обычную рабочую лошадку - функцию. В Lisp есть специальная функция, называемая setq, которая присваивает значение одного из своих аргументов другому аргументу. Чтобы в Lisp получить эквивалент операции x <-- 47, запишем следующее:

> (setq x 47)
47
> (setq y (+ 4 7))
11
> (+ 3 (setq z (* 3 7)))
24
>z
21
> (setq my-medium-of-expression "Lisp")
"Lisp"

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

> (setq month "August" day 04 year 2011)
2011
> day
04
> month
"August"

Lisp перед применением функции пытается вычислить аргументы. Когда мы вычисляем setq, символ (в нашем примере, х, у и z) может оказаться неопределенным. Это должно привести к ошибке - единственная причина, почему это не происходит, это то, что setq является экземпляром специальной функции. В Lisp-е аргументы каждой специальной функции могут обрабатываться по своим специальным правилам. Функция setq не пытается вычислить первый аргумент; второй аргумент обрабатывается так, как и обычно. К счастью, в Lisp совсем мало специальных функций.

Что дальше? Барабанная дробь ... (и если бы это был сериал, то сейчас можно было бы запустить и фейерверк) ... Макросы. После нескольких недель чтения литературы о макросах, вы, наконец, научитесь их писать. Для тех из вас, кто не знает, о чем я говорю, представьте себе агента Смита (программа искусственного интеллекта из фильма Матрица, носящая темные очки), превращающегося в самовоспроизводящийся компьютерный вирус. Если вы на секунду проигнорируете тот факт, что он в фильме был противником и ему никогда не нравились вирусы, и вместо этого сосредоточитесь на концепции программ, которые действительно могут писать программы, я уверен, что в начале следующего месяца вы будет ожидать выхода следующей моей статьи. И я с нетерпением жду ее, поскольку она приведет нас на один шаг ближе к нашим планам.

Продолжение следует...