Блог О пользователеracket-lang

Регистрация

 

Почему Lisp


Здесь я хочу сравнить семейство Lisp'ов с остальными языками программирования.

Итак:

  1. Арифметика. В Lisp есть бесконечная целочисленная арифметика (то есть, например, произведение чисел от 1 до 1000 — обычное число в отличие от C++ и Java). Также реализована точная рациональная арифметика, что позволяет производить большую часть вычислений без потери точности совсем, а если участвуют иррациональные числа, то с заданной точностью. В Си аналогичная возможность доступна только с библиотекой типа GMP.
  2. Управление памятью. Во всех лиспах есть сборка мусора. Во многих также есть слабые указатели, которые позволяют создавать ассоциативные массивы хранящие данные только до тех пор, пока хоть что-нибудь ссылается на ключ.
  3. Динамичность. Переменная может хранить любое значение. Функции, классы, методы классов можно добавлять и изменять не прерывая выполнения программы.
  4. Скорость. В среднем в два-три раза медленнее Java, в десять раз быстрее, чем Python или Perl.
  5. Синтаксис. В Lisp все выражения имеют структуру (команда параметр1 параметр2 … параметрn). То есть открывающая скобка, команда с параметрами, разделёнными пробелами и закрывающая скобка. Это на первый взляд непривычно. (a+b+c)*(d+e) будет записываться как (* (+ a b c) (+ d e)). С другой стороны, влух мы говорим как в лиспе «произведение суммы a,b,c и суммы d и e».
  6. Макросы. Макрос — функция преобразующая свои параметры в кусок кода. У Lisp здесь два преимущества: синтаксис позволяет кусок кода и параметры представлять в виде списков, а языковая сред позволяет в макросе использовать все функции, доступные в Lisp (включая использование переменных). Это позволяет, например, сделать работу с ООП в виде библиотеки (синтаксис определения класса, методов, … описывается макросами). Также можно генерировать код по внешним условиям: например, класс для доступа к БД по структуре таблицы в той БД. В других языках аналогичные цели достигаются с помощью IDE, что не даёт нужной гибкости.
  7. ООП. В Common Lisp реализована идея обобщённых функций (CLOS). Классы определяются отдельно. Методы классов могут быть определены (или изменены) позднее. Более того, метод может принадлежать не одному классу, а нескольким (проверка класса идёт по всем аргументам метода). Аналогичная функциональность в C++ достигается только паттерном Visitor (для двух классов) или через ручной перебор классов в методе. Также есть возможность указать действие до-, после- или вокруг основного действия метода, что позволяет легко добавлять проверку входных данных и журналировние к уже существующим иерархиям классов. В остальных лиспах аналог CLOS реализуется через макросы. Также есть ООП библиотеки с другой семантикой (передача сообщений и класс = пространоство имен, например).
  8. Особые переменные или параметры. В Common Lisp есть возможность указать, что переменная «особая» (special). В этом случае есть возможность указать значение переменной для заданного блока программы (и всех функций, которые вызываются из этого блока). Таким образом сочетаются достоинства глобальных переменных (можно передавать значение в функцию не указывая его явно в параметрах) без их недостатков (особая переменная меняет значение только в блоке, при доступе из другого потока или любом выходе из блока, в том числе при исключительной ситуации, значение особой переменной восстанавливается, блоки могут быть вложенными). В Racket особые переменные называются параметрами и имеют синтаксис функции (но семантика та же).
  9. Обработка исключительных ситуаций. В Lisp'ах традиционно при обработке исключительной ситуации, как правило, есть возможность не только освободить ресурсы и вернуть значение, но вместо этого указать, что можно продолжить работы с другими условиями. Например, при ошибке при чтении таблицы из файла можно пропустить значение, пропустить строку, повторить чтение файла сначала, прервать чтение.
  10. Удобная работа с замыканиями. Замыкания — это функция (или несколько) со ссылками на переменные, в окружении которых она определена. Позволяет действительно инкапсулировать данные (область видимости у переменных только внутри заданной функции и всё). На самом деле аналогичная задача решается и другими средствами. Для функции в Си можно сделать переменные с параметром static. Для нескольких функции или для замыкания, возвращаемого из функции можно сделать класс и объект соответственно. У замыканий есть два преимущества: простота написания (никаких лишних сущностей наподобие имени класса) и то, что они являются функциями. То есть везде, где можно передать функцию, можно передать замыкание. С методом класса такой фокус не проходит.

Таким образом, очевидно, что Lisp имеет гораздо больше возможностей и позволяет писать гораздо более надёжные программы. Так почему же он не завоевал популярности? Есть несколько причин:

  1. Историческая (суеверная). На Лиспе писали систему искуственного интеллекта. Поставленная задача решена не была. Разработчики назвали язык одной из причин неудачи. После этого ни один руководитель не рисковал выбрать Лисп для своего проекта.
  2. Синтаксис. Он непохож на Бейсик. Он непривычен для новичков. Более того, он расширяем, что значит, что в каждом проекте синтаксис может быть слегка другим.
  3. Библиотеки. На самом деле, нет библиотек, потому что нет популярности, а из-за этого нет библиотек. Это не критичная проблема, так как все современные лиспы могут использовать библиотеки, написанные на Си.

Ну и до недавнего времени не было хороших бесплатных компиляторов. Сейчас есть SBCL (Common Lisp), Racket (диалект Scheme), ABCL (Common Lisp на Java VM).


 

Для ответа с цитированием необходимо
выделить часть текста исходной записи

 
О пользователеdachter


Скорость. В среднем в два-три раза медленнее Java, в десять раз быстрее, чем Python или Perl.

Вы хотите сказать, что питон или перл в 20-30 раз медленнее джавы?
Сомнительно-с.


Таким образом, очевидно, что Lisp имеет гораздо больше возможностей и позволяет писать гораздо более надёжные программы.

Гораздо болье чем что? Гораздо более надежные чем на чем?


Библиотеки. На самом деле, нет библиотек, потому что нет популярности, а из-за этого нет библиотек. Это не критичная проблема, так как все современные лиспы могут использовать библиотеки, написанные на Си.

"Язык ничего не умеет, но это не проблема - можно привинтить подпорку из говна и палок".


Ну и до недавнего времени не было хороших бесплатных компиляторов. Сейчас есть SBCL (Common Lisp), Racket (диалект Scheme), ABCL (Common Lisp на Java VM).

Офигеть. Языку сто лет в обед, а нормальных компиляторов до сих пор нет. Ну вы прям рассадник энтузязизма.

 
О пользователеracket-lang

> Вы хотите сказать, что питон или перл в 20-30 раз медленнее джавы

Да, http://benchmarksgame.alioth.debian.org/u32/python.php

> Гораздо болье чем что? Гораздо более надежные чем на чем?

Мейнстримные языки: Java, C++, C#, Python, Perl, ...

> можно привинтить подпорку из говна и палок

Библиотеки на Си "из говна и палок"? Самокритичненько.

> Языку сто лет в обед, а нормальных компиляторов до сих пор нет.

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

 
О пользователеdachter


Угу, угу... Кучка искусственных тестов. Ну да ладно, бог с ним.

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


Мейнстримные языки: Java, C++, C#, Python, Perl, ...

Ну, мейнстримность перла уже под большим вопросом. 6-я версия еще не вышла, а 5-я обновляется шатко-валко.


Библиотеки на Си "из говна и палок"? Самокритичненько.

О, пытаетесь угадать мою профессию? Не стоит.

А из говна и палок - потому, что байндинги с внешними библиотеками всегда шаткие и часто неполные. Кроме того, их запаришься отгружать конечному пользователю (может оказаться, что у него уже стоит сишная библиотека более новой версии, и байндинг с ней не работает).


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

С логикой хорошо.
Приведено три компилятора, при этом упоминаются различные диалекты языка (common lisp и racket). Не удивлюсь, если разные компиляторы даже один диалект трактуют по-разному.

Использование диалекта - это привязка к конкретному компилятору, а возможно, и к определенной операционной системе (если компилятор не кросс-платформенный). Оно, конечно, не смертельно, но все равно не сильно мотивирует.

 
О пользователеracket-lang

> Дело в том, что оценивать языки только по производительности не совсем правильно. Производительность не сильно-то и важна. Гораздо важнее скорость разработки, наличие библиотек, поддержка на различных платформах, доступность компиляторов (интерпретаторов).

Это был скорее противовес мифу "Лисп ужасно медленный". Так было лет 30-40 назад. Тогда Си победил Лисп.

Скорость разработки как раз для лиспа на порядок выше (даже в геймдеве часто пишут прототип на лиспе, а затем переписывают на C++, так как там скорость -- это всё). Поддержка на разнах платформах: для Common Lisp'а почти где угодно (http://sbcl.org/platform-table.html например), для Racket -- windows, linux (включая arm), mac os, причём для Racket в стандартной библиотеке есть графический интерфейс.

Доступность компиляторов: свободные и бесплатные я перечислил. Для Common Lisp есть ещё уйма коммерческих.

> байндинги с внешними библиотеками всегда шаткие и часто неполные

Есть, конечно и байндинги, есть и переписывание на родной язык, но я имел в виду, что если мне в Racket вдруг понядобится printf, я не буду искать байндинг, а просто напишу (let ([printf (get-ffi-obj (ffi-lib "libc") "printf" (_fun _string _int _int - > _void))]) (printf "Test: %d %d\n" 42 69)). Аналогично в Common Lisp (чуть другой синтаксис, но кода где-то столько же).

> Использование диалекта - это привязка к конкретному компилятору, а возможно, и к определенной операционной системе

Common Lisp -- стандарт. Утверждён в 1994 году. Я привёл SBCL как бесплатный и с открытыми исходниками (лицензия BSD) и ABCL как версию для Java (соответственно можно использовать не только любую библиотеку на Си, но и любую на Java). Есть ещё большое количество коммерческих реализаций: http://en.wikipedia.org/wiki/Common_Lisp#Commercial_implementations

Racket -- изначально диалект Scheme. Сейчас фактически отдельный язык. Компилятор открытый (лицензия LGPL). Кроссплатформенность на Linux, Mac OS, Windows включая графические библиотеки, работу с сетью, базами данных.

 
О пользователеdachter

Во-от... Всего-то пару деньков общения с задаванием наводящих вопросов, и вы наконец пишете толковые тексты. Жаль, что пока в комментариях, а не в самом тексте статьи. :)

> Это был скорее противовес мифу "Лисп ужасно медленный". Так было лет 30-40 назад. Тогда Си победил Лисп.

Даю совет (пока бесплатный): хотите развенчать какой-то миф - прямо так и напишите. Мол, хочу развенчать миф такой-то (и далее - аргументы). Обходные пути и косвенные намеки никогда не работают. :)

> Скорость разработки как раз для лиспа на порядок выше
> Поддержка на разнах платформах:

Вот это и есть самое важное в вашей статье. Хм, окей, скажу прямо: самого-то важного в вашей статье вы и не написали. :)

> если мне в Racket вдруг понядобится printf, я не буду искать байндинг, а просто напишу (let ([printf (get-ffi-obj (ffi-lib "libc") ...

И что случится при запуске программы под Windows, где библиотеки libc.dll нет? :)

> Common Lisp -- стандарт. Утверждён в 1994 году.

Угу. Осталось выяснить, насколько стандарт проработан и нет ли в нем обширных "серых зон", оставленных на откуп разработчику компилятора.

 
О пользователеracket-lang


Вот это и есть самое важное в вашей статье.


Так это я и написал. В остальных девяти пунктах. Пункт про скорость приводился даже не как преимущество, а просто констатация факта. Не понимаю, почему ты именно к нему привязался.


И что случится при запуске программы под Windows, где библиотеки libc.dll нет

То же что и с аналогично программой на Си. Напишет "библиотека не найдена". К чему вопрос? Можно указывать несколько имён библиотек, где искать функцию.


нет ли в нем обширных "серых зон", оставленных на откуп разработчику компилятора


Не больше, чем в стандарте языка Си. Даже меньше. По крайней мере для Лиспа не приходится писать automake, чтобы он работал на любой реализации.

 
О пользователеАнонимно

> И что случится при запуске программы под Windows, где библиотеки libc.dll нет? :)

Распространять dll вместе с приложением, очевидно же.

 
О пользователеdachter

И куда ставить? В систему? Или рядом класть? :)

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

 
О пользователеАнонимно

> И куда ставить? В систему? Или рядом класть? :)

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