Продолжаем перевод эссе и книги Пола Грэма «Хакеры и Художники».
В конце статьи тех.директор компании 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
Сидит программист глубоко в отладке.
Подходит сынишка:
— Папа, почему солнышко каждый день встает на востоке, а садится на западе?
— Ты это проверял?
— Проверял.
— Хорошо проверял?
— Хорошо.
— Работает?
— Работает.
— Каждый день работает?
— Да, каждый день.
— Тогда ради бога, сынок, ничего не трогай, ничего не меняй!!!

Lisp широко использовался для создания экспертных систем еще на перфокартах, когда программирование только приходило в промышленность и оборонку. Поэтому есть «куски» инфраструктуры с суперответственной функцией, которые народ просто боится трогать, ибо может шарахнуть в масштабах страны.

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

Представьте себе сервак, с огроменной вычислительной мощностью, который обслуживает алгоритм, написанный под железо 30-ти летней давности, с низкой производительностью (Математический сопроцессор? — Не, не слышал). И масштабировать его можно только тупым наращиванием мощности. Потому что сам код дописывался вручную на протяжении десятилетий.

Тех. директор компании Edison: Мы портировали вычислительный модуль в C#, написанный давным-давно разработчиком на Lisp. Модуль состоял частично из экспертной системы, а частично из математики (преобразования Фурье, статистика). Lisp был выбран из-за того, что создатель алгоритма знал только его. Говорили, что он дорабатывал его чуть ли не всю жизнь.

Причина перехода — использовался старый компилятор Lisp, который не давал высокой производительности. Много лет расчеты на Lisp собирались в виде библиотеки, склеивались с современными программами и вызывались таким образом: в библиотеку передавались исходные матрицы, она надолго задумывалась и потом отдавала результат. Теперь заказчику хотелось использовать новые возможности Windows и ускорить выполнение расчета. Тем не менее, код хотелось сохранить без изменений, чтобы не испортить «эвристическую» часть.

Решение состояло из двух частей:
  • «дословное» переписывание кода, который содержал местами очень некачественные и даже корявые выражения, будто сделанные наспех.
  • оптимизация, которая сопровождалась проверкой новой библиотеки. Когда на вход алгоритма подавалось 30 различных схем исходных данных, а наша задача была в том, чтобы удостовериться в идентичности результата, полученного через старую (Lisp) и новую (C#) библиотеки.

Чтобы понимать охват — система развернута в масштабах страны и обрабатывала десятки тысяч объектов.

Комментарии (17)


  1. bigfatbrowncat
    17.11.2015 14:59
    +10

    Представьте себе сервак, с огроменной вычислительной мощностью, который обслуживает алгоритм, написанный под железо 30-ти летней давности, с низкой производительностью (Математический сопроцессор? — Не, не слышал). И масштабировать его можно только тупым наращиванием мощности. Потому что сам код дописывался вручную на протяжении десятилетий.


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


    1. Alexeyco
      17.11.2015 17:16
      +4

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

      Да мы каждый день такую поддерживаем… а переписать нельзя — потому как работает, много времени, приоритеты другие, Иванова (Петрова, Сидорова, Нусмухамедова — нужного подчеркнуть) нету на месте (переходит на новую должность, заболел, ушел в декрет, вышел из декрета), а он должен согласовать (передать, рассказать, как должно работать, исправить, запустить, дать доступ).


      1. bigfatbrowncat
        18.11.2015 14:52

        приоритеты другие

        Они могут внезапно и резко измениться :)


  1. sashkin
    17.11.2015 19:42
    +4

    Что-то я не понял смысла последнего раздела про древний проект на лиспе: сначала ода языку ушедшему вперёд задолго до мейнстримов, а в последних абзацах пример того, как проект с лиспа переписали на C#. Какой-то разрыв шаблона или я что-то не понял.


    1. taskmgr
      17.11.2015 23:10
      +1

      Аналогично


    1. PingWin
      18.11.2015 10:56
      -2

      Я так понимаю, речь о том, что Lisp — красивый академический язык. Интересно спроектированный, академически выверенный и т.д. К которому постепенно стремятся остальные языки/платформы.

      Но тем не менее широкой поддержки не снискавший, а прикладные вещи проще писать и поддерживать на широко распространённых платформах, т.к. поддерживать проще — да банально людей на рынке больше и они дешевле.


      1. loz
        18.11.2015 13:36
        +1

        Что, простите? Лисп красив академически с какой стороны? Мы ведь про CL говорим? У него куча совсем не академических, и тем более не выверенных особенностей типа системы рестартов или мутабельность структурок для производительности, подсказок компилятору и прочего.

        Что как бы совсем немного намекает на нацеленность на чисто практическое применение языка. И широту его поддержки ты как оценил? То, что на нем писали чуть менее чем все исследователи AI, а спонсировали и вкладывались в создание стандарта не столько университеты, сколько реальные корпорации и само мин обороны штатов говорит об обратном.


  1. RolexStrider
    17.11.2015 23:49
    -9

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

    Снова про «Покайтесь, грешники!» «Восславим же!» «О Аллилуйя!» ибо…
    ибо
    Ибо воистину. Первый Язык, жемчужина посреди простых камней, и нет языков кроме Него. Скобки, в которых пустота — тело Его, мистическое двуединство кода и данных — дух Его, божественная рекурсия — сердце Его. Истинно говорю вам, избегающий света Его есть безумец, вот, свершается кара над главой его, и убогостью отмечены поделия его, подобные пустым глиняным горшкам рядом с хрустальным сосудом благодати Его. Принявший же и постигший истинный свет Его подобен прямой и отточенной стреле, чисты помыслы его и крепка рука его, и благословенны творения его, дарующие радость и утоляющие печали, ибо одухотворены духом Его и отмечены благодатью Его.


  1. igrishaev
    18.11.2015 10:06
    +1

    В этой статье автор подробно описывает, почему переписал Реддит с Лиспа на Питон. Делает анализ, описывает плюсы и минусы. У вас же последний раздел ни о чем — ну переписали, и что с того.


  1. vba
    18.11.2015 10:32
    +4

    Сам последнее время вынужден писать на C# 6, но только что бы содержать семью. Но вот никак не могу взять в толк зачем переносить с Lisp на C# когда есть F#?


  1. PingWin
    18.11.2015 10:45
    -2

    А чтение во время выполнения позволяет программам общаться посредством S-выражений, что не так давно было заново изобретено как XML.

    Объясните кто-нибудь, при чём тут XML, как наличие функции типа «eval()» (а я так понимаю, речь идёт о ней?) помогает читать XML?
    Возможно, в оригинале речь шла о JSON или чём-то подобном?


    1. loz
      18.11.2015 15:42

      Вот тут про это очень подробно написано: www.defmacro.org/ramblings/lisp.html


      1. PingWin
        18.11.2015 15:54

        Ааа, подмена синтаксиса lisp под синтаксис xml… Забавно, но в продуктив такое же не пустишь — это ж будет огромная дырень в безопасности…
        Да и не очень понятно, при чём тут именно XML, так наверное можно любой текстовый формат читать… А теоретически и не только текстовый…


        1. loz
          18.11.2015 16:11

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


  1. loz
    18.11.2015 15:45
    +2

    Причина перехода — использовался старый компилятор Lisp, который не давал высокой производительности.

    Так а в чем была проблема просто взять новый, современный, быстрый компилятор типа SBCL? Хочется подробностей.


  1. PQR
    19.11.2015 10:33
    +5

    Надо было переписывать на Clojure — вот про такой опыт было бы интересно почитать!


  1. potan
    19.11.2015 19:57
    +3

    Сейчас есть куча компиляторов разных Lispов под разные платформы (в том числе и .net). Не понятно, почему понадобилось переписывать именно на C#?