Я программировал и строил большие системы на многих разных языках программирования и системах. Моя дипломная работа была написана на языке C, основанном на ядре Unix версии 7, выпущенном Bell Labs в 1970-х годах. Когда я приехал в США в начале 80-х, я работал в лабораториях и использовал C ++. В 1991 году я принял участие в проекте в Morgan Stanley, где я был единственным программистом на C ++, работавшим над проектом, построенным на Smalltalk, и когда я увидел, на что способен Smalltalk, это поразило меня. Я боролся с различными C ++ IDE, которые никогда не оправдали своих обещаний, и когда я увидел VisualWorks Smalltalk - я никогда не хотел делать что-либо еще. Это была полная смена парадигмы.

Итак, я работал в Smalltalk в течение следующих 15 лет или около того, примерно до 2007 года, в основном в секторе финансовых услуг Нью-Йорка. Я кое-что сделал на Java, но крах 2008 года положил конец моей карьере в Smalltalk. Так, что дальше?

В то время Ruby казалась ближе всего к Smalltalk, и я работал в Pivotal Labs около года или в нескольких других компаниях. Мне также удалось найти некоторые другие работы по Smalltalk, но никаких новых разработок - все они касались обслуживания и повышения производительности.

После этого я присоединился к стартапу и переписал существующее Java-приложение на Groovy. На этом этапе я также обнаружил Clojure, но нехватка времени и обстоятельства не позволили нам использовать Clojure в этом проекте. Как бы я хотел, чтобы все было по-другому.

У меня не было возможности по-настоящему покопаться до прошлого года, после того, как я немного по-настоящему изучил функциональные языки и написал пару приложений и значительную библиотеку (https://github.com/wizardpb/functional -vaadin ) Я могу однозначно сказать: Clojure - самая продуктивная среда, которую я когда-либо использовал.

Почему?

Меня сразу поразили 3 вещи:

  • Тестирование и совместимость.
  • Поддержка параллелизма.
  • Мульти-методы.

Тестирование, возможность комбинирования

Вы когда-нибудь пробовали TDD с ОО-языками? Как все прошло? Должно быть легко, правда? Напишите тест, затем выполните рефакторинг "красный-зеленый", готово. Извините, не так быстро. Проблема заключается в «Написать тест». Проблема не в понимании того, что должен делать тест, а в написании кода фикстуры и настройки.

ОО-программы состоят из объектов, объединенных фрагментов кода и состояния. Эти объекты организованы в сложные иерархии и сети ссылок между ними. Чтобы протестировать объект, объекты, на которые он ссылается, должны быть заменены каким-либо объектом, который может гарантировать получение известных результатов при вызове. Они реализуются с помощью макетов, приспособлений и заглушек. Проблема в том, что их настройка может быть сложной и трудоемкой задачей, часто намного большей, чем написание самого теста, и часто намного больше, чем написание тестируемого кода. Результат? В условиях нехватки времени реальной разработки происходит одно из двух: либо тесты написаны наполовину, либо вообще не написаны, либо архитектура приложения искажена и предназначена для легкого тестирования, а НЕ для правильного тестирования. , надежное и удобное в обслуживании решение решаемой проблемы. (Мартин Фаулер, Кент Бек и Дэвид Хайнемайер Ханссон провели очень интересный видеочат на эту тему - см. http://martinfowler.com/articles/is-tdd-dead/ ). Итог - выполнение реального TDD в среде OO сложно и требует много времени.

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

Теперь посмотрим, как это соотносится с понятием композиции функций (которая также проистекает из того факта, что функции не имеют побочных эффектов). Если вы знаете, что функция A верна, а функция B верна, и эта функция B будет возвращать только те значения, которые верны для функции A (все это можно продемонстрировать модульными тестами - особенно легко при использовании clojure.spec), тогда вы можете по правилам композиции функций утверждать, что A (B ()) также будет правильным. Это означает, что значительно сокращается количество тестов, которые необходимо написать для проверки правильности вашей системы.

Это результат, которым я очень доволен.

Поддержка параллелизма

Хотя Java действительно поддерживает параллельное программирование, ее модель потоков, блокировок и изменчивых переменных является громоздкой, подверженной ошибкам и очень сложной для отладки. Clojure идет намного дальше и использует несколько функций, чтобы упростить параллельное программирование и снизить вероятность ошибок. Во-первых, неизменяемые структуры данных позволяют с легкостью обмениваться данными между потоками - поскольку они неизменяемы, потоки не могут мешать другим потокам. Во-вторых, разница между value и identity четко и недвусмысленно определена. В объектно-ориентированной системе они смешанные - объекты имеют идентичность, и их значение изменяется путем изменения их состояния. Нет ощущения идентичности, которое ассоциируется с разными ценностями во времени. В Clojure идентичность обеспечивается явными конструкциями, значения которых (теперь неизменяемые) могут изменяться атомарно в течение определенного промежутка времени. Именно этот факт значительно упрощает параллельное программирование в Clojure.

Есть несколько способов сделать это.

Атомы допускают синхронные и независимые обновления. Функция swap! принимает атом и функцию, вызывает функцию с текущим значением и устанавливает ее значение равным возвращаемому значению этой функции. Если значение изменилось (другим потоком) во время обновления, операция повторяется - поменять местами! по сути, представляет собой операцию "тест и установка".

Ссылки и программная транзакционная память (STM) обеспечивают синхронные и скоординированные изменения. В этой схеме все обновления атомарны, согласованы и изолированы, как и транзакция базы данных. Все обновления и чтения ссылки должны выполняться с помощью функции dosync. Все вызовы функций внутри dosync видят стабильное состояние, при этом вся транзакция повторяется при обнаружении конфликта обновления.

Агенты предоставляют схему на основе сообщений, позволяющую выполнять асинхронные и независимые обновления. Агенты хранят одно значение и обновляются путем отправки сообщения агенту. Сообщения - это функции, и система агента гарантирует, что когда-нибудь в будущем функция сообщения будет вызываться с текущим значением агента и состоянием, обновленным с возвращаемым значением. Выполнение сообщения сериализуется системой агентов, поэтому обновления применяются последовательно.

Мульти-методы

Как и в объектно-ориентированных языках, Clojure имеет полиморфную отправку, реализованную с помощью нескольких методов, возможность отправки различным версиям функции на основе аргументов. В отличие от объектно-ориентированных языков, где отправка основана исключительно на типе получателя, Clojure идет намного дальше - даже дальше, чем другие Lisp, такие как CLOS. Множественные методы Clojure отправляются на основе возвращаемого значения функции отправки. Мульти-метод - это набор функций, имеющих одно и то же имя, которые совместно используют одну функцию диспетчеризации. Эта функция получает те же аргументы, что и вызов функции, и возвращает значение - значение отправки. Каждая из функций в наборе нескольких методов связана с определенным значением диспетчеризации, и когда вызывается мульти-метод, вызывается функция диспетчеризации, и ее возвращаемое значение определяет, какая версия мульти-метода вызывается.

Таким образом может быть определен полностью произвольный и подходящий полиморфизм. Полиморфизм больше не привязан к наследованию типов. Вместо этого прикладной программист может определить схему диспетчеризации для непосредственной поддержки решения проблемы. Это очень мощный инструмент, который позволяет получить более чистый и лаконичный код. Иерархии наследования также могут зависеть от приложения. Они определяются функцией derive на основе двух типов иерархий: наследование классов Java и определяемые приложением отношения с использованием функции isa?.

Мой опыт показывает, что эти три вещи позволяют писать более чистый, более быстрый и правильный код - и это делает Clojure самой производительной средой, в которой я когда-либо программировал. . Сейчас это единственный язык, на котором я хочу программировать.