Java.next

Выбор следующего языка для платформы JVM

Comments

Серия контента:

Этот контент является частью # из серии # статей: Java.next

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Java.next

Следите за выходом новых статей этой серии.

Я – неудавшийся поэт. Возможно, каждый романист сначала пытается писать стихи и, убедившись в своей неспособности делать это, он пробует себя в форме рассказа, которая по своей сложности стоит на втором месте после поэзии. И только потерпев поражение и здесь, он принимается за написание романов.
—Уильям Фолкнер (William Faulkner), лауреат Нобелевской премии по литературе

Далеко не каждый писатель — даже из числа великих — работает со всеми литературными формами. Точно так же обстоит дело и у разработчиков — некоторые языки программирования кажутся более естественными, чем другие. Некоторые разработчики являются прирожденными программистами на языке C, другие имеют природную склонность к языку Lisp, а третьи являются горячими поклонниками языка Perl. Тот факт, что ни один из языков программирования не соответствует в полной мере предпочтениям каждого разработчика, помогает объяснять существование столь большого количества языков. Для языков Java.next следствие состоит в том, что никакой единственный язык не будет "доминировать над ландшафтом", поскольку нет какого-либо одного языка, который устраивал бы всех разработчиков.

На первый взгляд пример язык Java свидетельствует об обратном. Однако это не так — доминирование Java явилось следствием уникального стечения обстоятельств — которое Брюс Тейт (Bruce Tate) в свой известной книге Beyond Java (За пределами Java) назвал "идеальным штормом". После появления языка Java в середине 1990-х годов он столкнулся с большими трудностями при внедрении. В то время он работал медленнее, чем популярные компилируемые языки. Он требовал много памяти (и это в то время, когда цены на память переживали временный резкий подъем). И он не особенно подходил для преобладающего в то время клиент-серверного стиля разработки. На стороне Java были лишь два благоприятных обстоятельства: относительная простота использования (благодаря таким механизмам, как сборка мусора) и апплеты, которые в то время поддерживались только в Java. Если бы обстановка не изменилась, язык Java вряд ли выжил бы.

Однако язык Java оказался идеальным партнером для только что возникшей тогда World Wide Web, особенно когда приобрели популярность интерфейсы Servlet API. Неожиданно оказалось, что модель развертывания на серверной стороне смягчает многие недостатки Java. Это сочетание факторов (аппаратные средства, Всемирная паутина, парадигма программирования) и стало "идеальным штормом по Тейту": разработчикам нужны были новые инструменты для веб-программирования, а использование в Java серверной стороны ослабило ограничения по памяти и предоставило простую модель для построения устойчивых веб-приложений. Благодаря тому, что язык Java оказался в нужном месте и в нужное время, а также благодаря поддержке крупной компании (Sun), он стал доминирующей силой в отрасли программного обеспечения.

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

Мультипарадигменные языки программирования

Многие новые языки поддерживают несколько парадигм программирования: объектно-ориентированное программирование, метапрограммирование, функциональное программирование, процедурное программирование и т. д. Из языков Java.next мультипарадигменными являются Groovy и Scala. Groovy — объектно-ориентированный язык с функциональными расширениями на основе библиотек. Scala — гибридный объектно-ориентированный и функциональный язык с акцентом на таких преимуществах функционального программирования, как иммутабельность (immutability) и отложенное исполнение (laziness).

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

Несмотря на свою мощь, мультипарадигменные языки предъявляют более строгие требования к дисциплине разработчиков в больших проектах. Такой язык поддерживает множество различных абстракций и принципов, поэтому изолированные друг от друга группы разработчиков вполне могут создать и поместить в библиотеки существенно различающиеся варианты. Например, повторное использование кода в объектно-ориентированном мире стимулирует применение структур, а в функциональном мире — композицию и функции высшего порядка. При проектировании API-интерфейса для своей компании необходимо выбрать наилучший стиль и убедиться в том, что все участники рабочей группы согласны с ним (и придерживаются его). С этой проблемой столкнулись многие разработчики, переходящие с Java на Ruby, поскольку Ruby является мультипарадигменным языком. C++, другой мультипарадигменный язык, породил проблемы во многих проектах, руководители которых неуклюже (а во многих случаях — непреднамеренно) попытались охватить и процедурную, и объектную ориентацию.

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

Некоторые языки, включая Clojure, повышают уровень дисциплинированности за счет преимущественного применения одной парадигмы, поддерживая другие парадигмы из прагматических соображений. Язык Clojure представляет собой в первую очередь "функциональный Lisp" для платформы JVM. Он допускает взаимодействие с классами и методами из нижележащей среды (и при необходимости позволяет создавать собственные), однако в первую очередь Clojure поддерживает строго функциональные парадигмы, такие как иммутабельность и отложенное исполнение.

Решающее достоинство: Функциональное программирование

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

В статье под названием Мемоизация и функциональный синергизм я перевел императивный метод indexOfAny() (из библиотеки StringUtils проекта Apache Commons) на язык Clojure, в результате чего получил более короткую и более простую функцию, имеющую при этом более общий характер. Программный код на языке Clojure вполне понятен для "посвященных", однако для непривычных к языку Lisp разработчиков он выглядит весьма странным. Язык Scala спроектирован таким образом, чтобы быть более понятным для Java-разработчиков. Этот же метод indexOfAny() лучше выглядит на Scala, чем на Clojure(см. листинг 1).

Листинг 1. Реализация метода indexOfAny() на языке Scala
def indexOfAny(input : Seq[Char], searchChars : Seq[Char]) : Option[Int] = {
  def indexedInput = (0 until input.length).zip(input)
  val result = for (char <- searchChars; 
       pair <- indexedInput; 
       if (char == pair._2)) yield (pair._1)
  if (result.isEmpty) 
    None
  else 
    Some(result.head)
}

Цель метода indexOfAny состоит в том, чтобы возвратить внутри первого параметра индексную позицию любого из символов, переданных во втором параметре. В листинге 1 я сначала генерирую коллекцию indexedInput посредством построения последовательного списка чисел на основе длины вводимой строки. Затем я использую встроенную в Scala функцию zip(), которая "сшивает" два списка вместе. Например, в случае входной строки zabycdxx, результат в коллекции indexedInput выглядит следующим образом: Vector((0,z), (1,a), (2,b), (3,y), (4,c), (5,d), (6,x), (7,x)).

Имея коллекцию indexedInput, я использую конструкцию for как замену вложенных циклов в первоначальной версии. Сначала я выполняю поиск в searchChars; я проверяю присутствие каждого из этих символов во второй части пары в коллекции indexedInput (с использованием Scala-сокращения pair._2)) и возвращаю индексную часть пары в случае совпадения (pair._1). Функция yield() генерирует значения для возвращаемого списка.

Для Scala типичным является возвращение Option вместо возможного null, поэтому я возвращаю или None (если результатов нет) или Some (в остальных случаях). Первоначальный метод indexOfAny() возвращает только индекс первого совпадающего символа, поэтому я возвращаю только первый элемент в результате (result.head). В Clojure-версии я возвращаю список всех совпадений. Scala-версию можно легко преобразовать таким образом, чтобы она делала то же самое (см. листинг 2).

Листинг 2. Метод indexOfAny, возвращающий все совпадения
def lazyIndexOfAny(input : Seq[Char], searchChars : Seq[Char]) : Seq[Int] = {
  def indexedInput = (0 until input.length).zip(input)
  for (char <- searchChars; 
       pair <- indexedInput; 
       if (char == pair._2)) yield (pair._1)
}

В листинге 2 возвращается список совпадений, а не только первое совпадение. Например, результатом lazyIndexOfAny("zzabyycdxx", "by") является Vector(3, 4, 5), содержащий индексы всех целевых символов во входной строке.

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

Функциональная пирамида

В общем случае типизацию в языках программирования можно классифицировать по двум осям: "сильная — слабая" и "динамическая — статическая" (см. рис. 1).

Рисунок 1. Характеристики типизации в языках программирования
Diagram of language typing characteristics
Diagram of language typing characteristics

В случае сильной типизации (т. е. строгого контроля типов) переменные "знают" свой тип (что обеспечивает возможность отражения и проверки экземпляра) и сохраняют это знание. Языки со слабой типизацией (т. е. с нестрогим контролем типов) в меньшей степени "осознают", на что именно они указывают. Например, C — это язык со статическим нестрогим контролем типов: переменные в C в действительности представляют собой коллекции битов, которые могут быть интерпретированы различными способами — к радости или к ужасу (а иногда и одновременно) C-программиста.

Java — язык со статическим строгим контролем типов. При объявлении переменных программист должен указывать типы переменных, иногда — неоднократно. В языке Java постепенно был реализован механизм вывода типов (type inference), однако по лаконичности в отношении типов язык Java даже не приближается ни к одному из языков Java.next. Scala, C# и F# также являются языками со строгим статическим контролем типов, но они обходятся гораздо меньшим количеством слов благодаря использованию вывода типов. Язык способен многократно распознавать надлежащий тип, что уменьшает избыточность.

Эти различия существовали с самого начала существования языков программирования. Однако теперь появился новый аспект: функциональное программирование.

Как было показано в статье Стили написания функционального кода, у языков функционального программирования принципы проектирования отличаются от таковых у императивных языков. Императивные языки пытаются упростить мутирование состояния и содержат много средств для достижения этой цели. Функциональные языки пытаются минимизировать мутабельные состояния и создать "машинерию" более общего назначения. Однако функциональность не диктует систему типизации (рис. 2).

Рисунок 2. Языки функционального программирования
Illustration showing that some functional programming languages are dynamically typed and some are statically typed
Illustration showing that some functional programming languages are dynamically typed and some are statically typed

Языки функционального программирования полагаются на иммутабельность (а иногда и настаивают на ней). Ключевое различие между языками теперь пролегает не по оси "динамический — статический", а по оси "императивный — функциональный". Это порождает интересные последствия с точки зрения способов создания программного обеспечения.

В 2006 году в своем блоге я непредумышленно вернул популярность термину Многоязычное программирование и придал ему новый смысл: использование преимуществ современных сред исполнения для создания приложений, сочетающих языки, а не платформы. Это новое определение базировалось на понимании того, что платформы Java и .NET поддерживают более 200 языков, а также на подозрении, что не существует никакого одного "истинного языка", способного решить любую задачу. Современные управляемые среды исполнения позволяют беспрепятственно сочетать языки на уровне байт-кода, используя для каждой конкретной работы наиболее подходящий из этих языков.

После того как я опубликовал упомянутую статью в своем блоге, мой коллега Ола Бини (Ola Bini) опубликовал ответную статью, в которой описал свою многоязычную пирамиду. Эта пирамида, показанная на рис. 3, предлагает способ структурирования приложений в многоязычном мире.

Рисунок 3. Пирамида Бини
Ola Bini's Polyglot Pyramid

В своей перевернутой пирамиде Бини предлагает использовать более статические языки на самых нижних уровнях, где самый высокий приоритет имеет надежность. Затем он предлагает использовать более динамические языки для уровней приложений, применяя более простой синтаксис для построения таких артефактов, как пользовательские интерфейсы. И, наконец, на самом верху он предлагает использовать предметно-ориентированные языки (domain-specific language, DSL), создаваемые разработчиками с целью компактной формализации важных знаний определенной предметной области и соответствующего потока работ. Как правило, DSL-языки реализуются на основе динамических языков и с привлечением некоторых из их релевантных возможностей. Цель Бини состояла в том, чтобы повысить степень определенности на нижних уровнях и степень гибкости на верхних уровнях.

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

Рисунок 4. Функциональная пирамида
The functional pyramid created by Neal Ford
The functional pyramid created by Neal Ford

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

Поверх функционального ядра используются императивные языки для обращения с потоком работ, с бизнес-правилами, с пользовательскими интерфейсами и с другими частями системы, для которых приоритетом является простота разработки. Как и в первоначальной пирамиде, на самом верху находятся DSL-языки, которые служат тем же целям. Тем не менее я также полагаю, что DSL-языки проникнут на все уровни наших систем, вплоть до самого низа. Это подтверждается той простотой, с которой можно писать DSL-языки на таких языках, как Scala (функциональный язык со статическим строгим контролем типов) и Clojure (функциональный язык с динамическим строгим контролем типов) для компактной формализации важных концепций.

Построение приложений, придерживающихся этой пирамиды, потребует значительных изменений, однако перспективы выглядят весьма впечатляющими. Чтобы получить некоторое представление об этих возможностях, посмотрите на архитектуру Datomic (коммерческий продукт). Datomic — это функциональная база данных, которая сохраняет полную и точную историю каждого изменения. Любое обновление не уничтожает данные, а создает новую версию базы данных. Эту базу данных можно откатывать назад по шкале времени, чтобы увидеть моментальные снимки из прошлого. Доступ ко всей истории делает тривиальными такие практики, как непрерывное развертывание программного обеспечения (которое базируется на возможности прокручивать базу данных назад и вперед по шкале времени). Также становится тривиальным тестирование множественных версий приложения, поскольку вы можете непосредственно синхронизировать изменения схемы и кода. Продукт Datomic построен с помощью Clojure, с использованием функциональных конструкций на архитектурном уровне. И результаты на верхнем уровне весьма впечатляют.

Заключение

Это заключительная статья в цикле Java.next. Я надеюсь, что этот цикл пробудил ваш интерес к более глубокому изучению языков и концепций, которые я затронул в предыдущих пятнадцати статьях. С тех пор, как я начал этот цикл восемнадцать месяцев назад, ситуация в области языков программирования изменилась. На арену Java.next вышел новый сильный соперник — язык Java 8. В нем наконец реализованы базовые элементы функционального программирования, которые будут доминировать в языковой среде в следующие несколько лет. У всех четырех языков Java.next (Groovy, Scala, Clojure и Java 8) имеются сильные сообщества и растущая пользовательская база, генерирующие непрекращающийся поток новых идей. Перспективы для языков JVM выглядят многообещающими — вне зависимости от того, какой из них — или их комбинацию — вы выберете.


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи: Java.next: Choosing your next JVM language.
  • Beyond Java Брюс Тейт (Bruce Tate), издательство O'Reilly Media, 2005 г. Прочтите материал об "идеальном шторме", который обусловил доминирование языка Java.
  • Scala: современный функциональный язык, работающий на платформе JVM.
  • Clojure: современный функциональный вариант Lisp, работающий на платформе JVM.
  • Functional Thinking (Функциональное мышление), Нил Форд (Neal Ford), издательство O'Reilly Media, 2014 г. Узнайте больше о функциональном программировании из этой книги, написанной автором данного цикла.
  • Polyglot Programming (Многоязычное программирование). Статья в блоге Нила Форд (Neal Ford), 2006 г.
  • Fractal Programming (Фрактальное программирование). Статья в блоге Ола Бини (Ola Bini) о фрактальном программировании и о многоязычной пирамиде.
  • Datomic: познакомьтесь с архитектурой продукта Datomic (иммутабельный сервер баз данных, написанный на языке Clojure).
  • Функциональное мышление: колонка Нила Форда на developerWorks, посвященная функциональному программированию.
  • Записная книжка дизайнера языка: в этом цикле статей developerWorks Java-архитектор Брайан Гетц исследует некоторые проблемы дизайна языка, вызвавшие трудности при эволюции языка Java версий Java SE 7, Java SE 8 и далее.
  • IBM SDK, Java Technology Edition Version 8: Примите участие в программе бета-тестирования IBM SDK for Java 8.0.

Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=987338
ArticleTitle=Java.next: Выбор следующего языка для платформы JVM
publish-date=10282014