В конце статьи тех.директор компании Edison рассказывает, как они портировали Lisp на С#
«Мы гонялись за С++ программистами. Нам удалось перетащить их целую кучу на полпути к Lisp.»
Гай Стил, соавтор Java спецификации.
Оригинал — Revenge of the Nerds, Май 2002
и What Made Lisp Different, декабрь 2001
За перевод спасибо Щёкотовой Яне.
Начало: Пол Грэм: «Месть ботанов», часть 1
Часть вторая
Чем отличается Lisp
Когда Lisp был впервые разработан, он воплощал в себе 9 новых принципов. Сегодня некоторые из них мы воспринимаем как само собой разумеющееся, другие можно увидеть только в более продвинутых языках, а два все еще остаются прерогативой Lisp. Эти 9 принципов перечислены ниже в порядке их применения в основном IT-течении.
1 Условные высказывания (Conditionals). Условное высказывание представляет собой конструкцию if-then-else. Сегодня для нас это стандарт, но вот в Fortran I они отсутствовали. Там был только условный переход goto, на основе низлежащей машинной инструкции.
2 Функциональный тип данных (A function type). В Lisp функции – это такой же тип данных, как integer или string. Они имеют буквенное представление, могут быть присвоены переменным, могут передаваться в качестве аргументов, и т.д.
3 Рекурсия (Recursion). Lisp был первым языком программирования, который ее поддерживал.
4 Динамическое типизирование (Dynamic typing). В Lisp все переменные, в сущности своей, являются указателями. Значения – вот что имеет тип, а не переменные, и присвоение или связывание переменных означает копирование указателей, а не того, на что они указывают.
5 Сборка мусора (Garbage-collection).
6 Программы, составленные из выражений (Programs composed of expressions). Программы на Lisp представлены в виде деревьев выражений, каждое из которых возвращает значение. Это как противопоставление Fortran и большинству последующих языков, которое различает понятия «выражения» и «операторы».
Для Fortran I это различие было естественным, потому что операторы не могли быть вложенными. И таким образом, пока для работы нужны были математические выражения, не было смысла в создании чего-то такого, что возвращало бы значение, т.к. это значение никуда не могло быть сохранено.
Такое ограничение ушло в прошлое с появлением языков с блочной структурой, но к тому моменту было уже слишком поздно. Различие между выражениями и операторами сильно укоренилось и распространилось с Fortran на Algol, а затем и на всех их потомков.
7 Символьный тип (symbol type). Символы по факту являются указателями на строки, сохраненные в хеш-таблицах. Таким образом, проверять равенство можно путем сравнения указателей, а не каждого символа.
8 Нотации для кода, использующего деревья символов и констант.
9 Постоянная целостность языка. В действительности различия между временем чтения кода, временем компиляции и временем выполнения нет. Можно компилировать или запускать код во время чтения, читать или запускать код во время компиляции, и читать или компилировать код во время его выполнения.
Запуск кода во время чтения позволяет пользователям перепрограммировать синтаксис Lisp. Запуск кода во время компиляции – это базовый принцип макроса. Компиляция во время выполнения – это принцип использования Lisp в качестве языка расширения в программах подобных Emacs. А чтение во время выполнения позволяет программам общаться посредством S-выражений, что не так давно было заново изобретено как XML.
Когда Lisp впервые появился, эти принципы были далеко за пределами обычной практики программирования, что, в основном, было продиктовано аппаратурой, появившейся в конце 1950-ых годов. Со временем, базовый язык, воплощенный в последователях популярных языков, постепенно эволюционировал в Lisp. Принципы 1-5 сейчас широко распространены. Принцип под номером 6 еще только начинает проявляться в мейнстриме. Python находится на 7 стадии, хотя, кажется, для этого принципа отсутствуют какие-либо синтаксические правила.
Что касается пункта 8, то он, возможно, является одним из самых интересных. Принципы 8 и 9 стали частью Lisp случайно, потому что Стив Рассел внедрил то, что Маккарти никогда и не собирался делать. И все же, эти принципы оказались лежащими в основе как странного появления Lisp, так и его наиболее отличительных черт. Lisp выглядит настолько странно не потому что у него своеобразный синтаксис, а потому что у него нет синтаксиса как такового. Вы пишете программы прямо в деревьях грамматического разбора, которые строятся за кулисами во время парсинга в других языках программирования, и эти деревья состоят из списков, которые являются структурами данных в Lisp.
Выражение языка в его собственных структурах данных оказывается довольно мощным свойством. Принципы 8 и 9 в совокупности означают, что можно написать программы, которые пишут программы. Это, возможно, звучит как бред, но в Lisp это обычное дело. Наиболее распространенным способом для осуществления этого является макрос.
Макрос (в контексте Lisp) все еще, насколько мне известно, редкость для языка Lisp. Частично это потому, что, чтобы написать макрос, Вам, вероятно, придется создать синтаксис Вашего языка программирования таким же странным как и у Lisp. Это также может быть потому, что если Вы внедрите этот финальный штрих, то больше нечего и ожидать разработок нового языка, а только нового диалекта Lisp.
В основном, я это все преподношу как шутку, но в ней есть доля правды. Если определить язык, в котором есть car, cdr, cons, quote, cond, atom, eq и нотация для функций, выраженных в виде списков, тогда из всего этого можно построить все остальное нутро Lisp. Это, на самом деле, и есть определяющее качество Lisp: все было организовано так, чтобы Маккарти придал Lisp ту форму, которую он имеет сейчас.
Где языки имеют значение
Итак, предположим, Lisp представляет некоторое ограничение, к которому асимптотически приближаются популярные языки. Означает ли это, что следует использовать этот язык для написания программного обеспечения? Сколько Вы теряете на использовании менее мощного языка? Не лучше ли, иногда, оставаться в стороне от передовых инноваций? И разве популярность до некоторой степени не является своим собственным обоснованием? Разве невежественный начальник не прав, например, в том, чтобы использовать язык, для которого он сможет легко нанять программистов?
Конечно, существуют проекты, где выбор языка программирования не имеет значения. Как правило, чем требовательнее приложение, тем больший выигрыш Вы получаете от использования мощного языка. Но множество проектов не настолько требовательные. Большая часть процесса программирования состоит из написания небольших программ-посредников, где можно использовать любой язык, с которым Вы уже знакомы, и у которого есть хороший набор библиотек для любых Ваших целей. Если Вам нужно только передать данные из одной Windows программы в другую, конечно же, используйте Visual Basic.
Вы можете также писать программы-посредники и на Lisp (я его использую как настольный калькулятор), но наибольшая выгода от использования языков подобных Lisp в другом спектре применения, где Вам нужно написать сложные программы для решения сложных задач в условиях жесткой конкуренции. Хорошим примером является программа поиска цен на авиаперелеты, право на использование которой ITA Software предоставила компании Orbitz. Эти ребята вошли на рынок, где уже доминировали два крупных сильных противника, Travelocity и Expedia, и, казалось, просто подавили их с технологической точки зрения.
Ядро приложения ITA составляют 200 000 строк программы на Common Lisp, которая ищет на порядок больше возможностей, чем их конкуренты, все еще, по-видимому, использующие технологии эпохи программирования мейнфреймов. (Хотя ITA также в некотором смысле использует язык программирования эпохи мейнфреймов). Я ни разу не видел ни строчки кода из программы ITA, но, согласно одному из их лучших специалистов, они используют много макросов, чему я нисколько не удивляюсь.
Продолжение следует
(Кто хочет помочь с переводами статей Пола Грэма — пишите в личку)
Подходит сынишка:
— Папа, почему солнышко каждый день встает на востоке, а садится на западе?
— Ты это проверял?
— Проверял.
— Хорошо проверял?
— Хорошо.
— Работает?
— Работает.
— Каждый день работает?
— Да, каждый день.
— Тогда ради бога, сынок, ничего не трогай, ничего не меняй!!!
Lisp широко использовался для создания экспертных систем
Интерфейс, обертку, математику — все можно переписать, а вот переписать экспертную систему — это связано с большими политическими, организационными и репутационными рисками и ответственностью, если в результате что-то пойдет не так.
Представьте себе сервак, с огроменной вычислительной мощностью, который обслуживает алгоритм, написанный под железо 30-ти летней давности, с низкой производительностью (Математический сопроцессор? — Не, не слышал). И масштабировать его можно только тупым наращиванием мощности. Потому что сам код дописывался вручную на протяжении десятилетий.
Тех. директор компании Edison: Мы портировали вычислительный модуль в C#, написанный давным-давно разработчиком на Lisp. Модуль состоял частично из экспертной системы, а частично из математики (преобразования Фурье, статистика). Lisp был выбран из-за того, что создатель алгоритма знал только его. Говорили, что он дорабатывал его чуть ли не всю жизнь.
Причина перехода — использовался старый компилятор Lisp, который не давал высокой производительности. Много лет расчеты на Lisp собирались в виде библиотеки, склеивались с современными программами и вызывались таким образом: в библиотеку передавались исходные матрицы, она надолго задумывалась и потом отдавала результат. Теперь заказчику хотелось использовать новые возможности Windows и ускорить выполнение расчета. Тем не менее, код хотелось сохранить без изменений, чтобы не испортить «эвристическую» часть.
Решение состояло из двух частей:
- «дословное» переписывание кода, который содержал местами очень некачественные и даже корявые выражения, будто сделанные наспех.
- оптимизация, которая сопровождалась проверкой новой библиотеки. Когда на вход алгоритма подавалось 30 различных схем исходных данных, а наша задача была в том, чтобы удостовериться в идентичности результата, полученного через старую (Lisp) и новую (C#) библиотеки.
Чтобы понимать охват — система развернута в масштабах страны и обрабатывала десятки тысяч объектов.
Комментарии (17)
sashkin
17.11.2015 19:42+4Что-то я не понял смысла последнего раздела про древний проект на лиспе: сначала ода языку ушедшему вперёд задолго до мейнстримов, а в последних абзацах пример того, как проект с лиспа переписали на C#. Какой-то разрыв шаблона или я что-то не понял.
PingWin
18.11.2015 10:56-2Я так понимаю, речь о том, что Lisp — красивый академический язык. Интересно спроектированный, академически выверенный и т.д. К которому постепенно стремятся остальные языки/платформы.
Но тем не менее широкой поддержки не снискавший, а прикладные вещи проще писать и поддерживать на широко распространённых платформах, т.к. поддерживать проще — да банально людей на рынке больше и они дешевле.loz
18.11.2015 13:36+1Что, простите? Лисп красив академически с какой стороны? Мы ведь про CL говорим? У него куча совсем не академических, и тем более не выверенных особенностей типа системы рестартов или мутабельность структурок для производительности, подсказок компилятору и прочего.
Что как бы совсем немного намекает на нацеленность на чисто практическое применение языка. И широту его поддержки ты как оценил? То, что на нем писали чуть менее чем все исследователи AI, а спонсировали и вкладывались в создание стандарта не столько университеты, сколько реальные корпорации и само мин обороны штатов говорит об обратном.
RolexStrider
17.11.2015 23:49-9Вы пишете программы прямо в деревьях грамматического разбора, которые строятся за кулисами во время парсинга в других языках программирования, и эти деревья состоят из списков, которые являются структурами данных в Lisp.
было организовано так, чтобы Маккарти придал Lisp ту форму, которую он имеет сейчас.
Снова про «Покайтесь, грешники!» «Восславим же!» «О Аллилуйя!» ибо…
ибоИбо воистину. Первый Язык, жемчужина посреди простых камней, и нет языков кроме Него. Скобки, в которых пустота — тело Его, мистическое двуединство кода и данных — дух Его, божественная рекурсия — сердце Его. Истинно говорю вам, избегающий света Его есть безумец, вот, свершается кара над главой его, и убогостью отмечены поделия его, подобные пустым глиняным горшкам рядом с хрустальным сосудом благодати Его. Принявший же и постигший истинный свет Его подобен прямой и отточенной стреле, чисты помыслы его и крепка рука его, и благословенны творения его, дарующие радость и утоляющие печали, ибо одухотворены духом Его и отмечены благодатью Его.
igrishaev
18.11.2015 10:06+1В этой статье автор подробно описывает, почему переписал Реддит с Лиспа на Питон. Делает анализ, описывает плюсы и минусы. У вас же последний раздел ни о чем — ну переписали, и что с того.
vba
18.11.2015 10:32+4Сам последнее время вынужден писать на C# 6, но только что бы содержать семью. Но вот никак не могу взять в толк зачем переносить с Lisp на C# когда есть F#?
PingWin
18.11.2015 10:45-2А чтение во время выполнения позволяет программам общаться посредством S-выражений, что не так давно было заново изобретено как XML.
Объясните кто-нибудь, при чём тут XML, как наличие функции типа «eval()» (а я так понимаю, речь идёт о ней?) помогает читать XML?
Возможно, в оригинале речь шла о JSON или чём-то подобном?loz
18.11.2015 15:42Вот тут про это очень подробно написано: www.defmacro.org/ramblings/lisp.html
PingWin
18.11.2015 15:54Ааа, подмена синтаксиса lisp под синтаксис xml… Забавно, но в продуктив такое же не пустишь — это ж будет огромная дырень в безопасности…
Да и не очень понятно, при чём тут именно XML, так наверное можно любой текстовый формат читать… А теоретически и не только текстовый…loz
18.11.2015 16:11Не подмена, а переизобретение интерпретации в разных применениях где это удобно. Про дырень в безопасности — это зависит от многих факторов, запуск кода в безопасном окружении никто не отменял. Плюс не обязательно именно любой исполняемый код разрешать — можно его и валидировать и что угодно делать, зависит от требований, времени и фантазии.
loz
18.11.2015 15:45+2Причина перехода — использовался старый компилятор Lisp, который не давал высокой производительности.
Так а в чем была проблема просто взять новый, современный, быстрый компилятор типа SBCL? Хочется подробностей.
PQR
19.11.2015 10:33+5Надо было переписывать на Clojure — вот про такой опыт было бы интересно почитать!
potan
19.11.2015 19:57+3Сейчас есть куча компиляторов разных Lispов под разные платформы (в том числе и .net). Не понятно, почему понадобилось переписывать именно на C#?
bigfatbrowncat
Представил. Это — кошмар вселенского масштаба, от которого надо уходить любой ценой. Постепенно, модуль за модулем переписывая код, обкладывая каждый старый и новый модуль тысячами самых заковыристых Unit-тестов. Потому что ситуация, при которой древняя программа принимает решения за людей, которые не понимают, как она работает — классический сюжет для фильма в жанре «техногенный апокалипсис».
Alexeyco
> Потому что ситуация, при которой древняя программа принимает решения за людей, которые не понимают, как она работает — классический сюжет для фильма в жанре «техногенный апокалипсис».
Да мы каждый день такую поддерживаем… а переписать нельзя — потому как работает, много времени, приоритеты другие, Иванова (Петрова, Сидорова, Нусмухамедова — нужного подчеркнуть) нету на месте (переходит на новую должность, заболел, ушел в декрет, вышел из декрета), а он должен согласовать (передать, рассказать, как должно работать, исправить, запустить, дать доступ).
bigfatbrowncat
Они могут внезапно и резко измениться :)