Основы функционального программирования с примерами на Scala — часть 2


Оглавление (нажмите, чтобы открыть):

Функциональное программирование со Scala: введение

Если вас интересует функциональное программирование, в этой статье вы найдете несколько важных аспектов этого направления и особенностей его работы в Scala.

Классические программы порождают концептуальные ограничения на использование модульной организации. Функциональные языки минимизируют эти ограничения. — Джон Хьюз, «Почему функциональное программирование значимо»

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

Что еще более важно, эта парадигма является естественной для параллельных вычислений. Уже в течение нескольких лет прирост скорости процессоров остается небольшим. Тем не менее, системы работают быстрее за счет роста числа ядер, работающих параллельно.

Почему Scala?

Scala – это язык с полной поддержкой функционального программирования, а также объектно-ориентированной парадигмы. Это один из наиболее широко используемых языков в функциональном сообществе, на высоком уровне наряду с таким языками как F# и Haskell, Clojure и других. Scala исполняется на JVM и отлично сочетается с Java, Groovy, Clojure и т.д.

Чистые функции

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

Нечистые функции

Нечистые функции позволяют влиять на внешний и/или внутренний контекст.

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

Функции высшего порядка

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

  • принять функцию, вернуть значение
  • принять значение, вернуть функцию
  • принять функцию, вернуть функцию

Эти функции не только сокращают программы, но также помогают создавать более чистый код.

Неизменяемость

Неизменяемость относится к данным внутри объекта, который не может быть изменен после его создания.

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

Эти примеры сравнивают преимущества, предлагаемые Scala в отличие от объектно-ориентированного языка, подобного Java,

Монады в Scala

Монада — это интерфейс, который просто определяет единый формат для составления данных.

Монады должны подчиняться определенным правилам. Scala не столь строга с этими законами, а ее ориентированность более практична. С точки зрения языка единственными чертами, которые должна иметь Монада, являются функции высокого порядка flatMap и map. Они предоставляют инструмент для составления данных, которые в них содержатся, с помощью применения функции.

Вероятно, проще объяснить это на примере,

Представьте себе на мгновение, что все известные нам коллекции является Монадой (в терминах Scala). Тогда мы можем составлять внутренние данные в этих коллекциях просто с помощью функции map или flatMap.

Резюме

В этом сообщении просто описывается некоторые из наиболее важных аспектов функционального программирования на Scala. Существует множество тем, требующих изучения. Например, специфические для языка Монады (Option, Future & Try) или «for-генераторы»(«for-comprehension») и «применить» («apply») методы для упрощения программ.

Кроме того, важно узнать больше о некоторых из наиболее широко используемых фреймворках, таких как Play!, Finagle и Akka.

Я искренне надеюсь, что мне удалось вызвать у вас интерес к Scala и функциональному программированию.

Часть 2. Cоздание калькулятора

Комбинаторы парсеров в Scala

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

Этот контент является частью # из серии # статей: Путеводитель по Scala для Java-разработчиков

Этот контент является частью серии: Путеводитель по Scala для Java-разработчиков

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

На настоящий момент ситуация выглядит следующим образом: в процессе создания DSL (в нашем случае – простого языка арифметических выражений) мы определили структуру AST со следующими типами вершин:

  • бинарные операторы сложения, вычитания, умножения и деления;
  • унарный оператор смены знака (отрицания);
  • числовые константы.

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

При этом код интерпретатора выглядит следующим образом (листинг 1).

Листинг 1. AST и интерпретатор языка выражений

Те из вас, кто прочитал предыдущую статью, помнят, что в качестве упражнения было предложено усовершенствовать оптимизацию, чтобы упрощение выражений выполнялось на произвольном уровне дерева, а не только на верхнем, как в листинге 1. Наиболее простой вариант был предложен Лексом Спуном (Lex Spoon). Он заключается в том, что функция simplify сначала вызывается рекурсивно для каждого из операндо, а затем выполняется упрощение самой операции на более высоком уровне (листинг 2).

Листинг 2. Усовершенствованная функция упрощения выражений

Синтаксический разбор

На данный момент перед нами стоит вторая часть задачи по созданию DSL – создание программного модуля, преобразующего выражение, заданное в текстовом виде, в AST. Этот процесс известен под именем «синтаксический разбор» и состоит из нескольких фаз: разбиение на токены, лексический анализ и синтаксический анализ.

Исторически существуют два способа создания программ для синтаксического разбора (парсеров):

  • вручную;
  • при помощи автоматизированных средств (генераторов парсеров).

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

Альтернативой служит использование генераторов парсеров. Исторически наибольшую популярность получили два генератора – lex (генератор лексических анализаторов) и yacc (Yet Another Compiler Compiler – генератор синтаксических анализаторов). Благодаря этим программам разработчикам парсеров не приходится создавать их вручную, достаточно передать на вход lex специальное описание лексики языка, после чего тот автоматически создаст самую низкоуровневую часть будущего парсера. Затем полученный лексический анализатор вместе с файлом, описывающим грамматические правила языка, передаются на вход yacc, который генерирует код парсера (грамматика языка определяет, в частности, список ключевых слов, правила описания блоков кода и т. д.).

Этот процесс подробно описан в учебниках по теории вычислительных наук, поэтому мы не будем углубляться в детали конечных автоматов и парсеров LARL- и LR-грамматик. Те из вас, кому интересны эти подробности, могут обратиться к статьям и книгам по данной тематике.

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

Комбинаторы парсеров

Для понимания целей создания и возможностей комбинаторов парсеров желательно определенное знакомство с таким способом описания грамматики языка, как форма Бэкуса-Наура (Backus-Naur Form – БНФ). Например, наш язык арифметических выражений можно описать в БНФ следующим образом (листинг 3).

Листинг 3. Описание языка

В БНФ элемент в левой части каждого выражения задает имя для набора различных вариантов входных данных. Элементы в правой части представляют собой константы и выражения языка, а также их сочетания. Если вас интересует более подробная информация о БНФ, то лучше обратиться к книгам таких авторов как Ахо/Лам/Сети/Ульман, ссылки на которые приведены в разделе Ресурсы.

Одним из преимуществ описания языка в БНФ является то, что от БНФ легче перейти к комбинаторам парсеров в Scala. Обратите внимание на листинг 4, в котором показана упрощенная форма БНФ из листинга 3.

Листинг 4. Упрощенная грамматика языка

В этой записи фигурные скобки ( <> ) обозначают возможность повторения (ноль или более раз), а вертикальная черта ( | , также известная как «конвейерный символ») – отношение типа «или». Таким образом, в листинге 4 записано, что выражение factor может быть либо выражением типа floatingPointNumber (определение этого правила опущено), либо выражением типа expr , заключенным в круглые скобки.

Грамматику, записанную в такой форме, легко трансформировать в парсер на Scala (листинг 5).

Листинг 5. От БНФ к парсеру

Преобразование фактически заключается в замене грамматических правил в БНФ на синтаксические конструкции комбинаторов: пробелы меняются на методы

, обозначающие последовательность, повторы – на метод rep , а «или» по-прежнему представляются в виде | . Строковые константы также остаются без изменений.

Своим удобством данный подход частично обязан двум вещам: стандартному базовому классу JavaTokenParsers , от которого унаследован класс парсера, и комбинатору floatingPointNumber . Первый, в свою очередь, является наследником других классов, которые полезны в случае, если анализируемый язык не столь хорошо сочетается с синтаксическими конструкциями в Java. Второй же берет на себя детали разбора десятичной записи чисел с плавающей точкой.

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

Принцип работы комбинатора парсеров

Для того чтобы понять принципы работы показанного выше парсера, необходимо ненадолго погрузиться в вопросы реализации комбинаторов. По своей природе каждый «парсер» представляет собой функцию (или case-класс), которая получает некоторые входные данные и возвращает объект-парсер. Например, комбинаторы самого нижнего уровня базируются на простых парсерах, которые принимают на вход объекты, считывающие элементы из входного потока (экземпляры Reader ) и возвращающие объекты, обладающие некоторый высокоуровневой семантикой (экземпляры Parser ). Пример показан в листинге 6.

Листинг 6. Пример простейшего парсера

Таким образом, Elem является абстрактным типом для представления любого вида входных данных, которые можно подвергнуть синтаксическому разбору. Как правило, таковыми являются текстовые строки или потоки. Тип Input представляет собой Elem , заключенный внутрь scala.util.parsing.input.Reader (квадратные скобки означают, что Reader является параметризованным типом данных, они играют роль, аналогичную угловым скобкам в C++ или Java). Наконец, Parser типа T – это такой тип данных, который принимает на вход экземпляр Input и возвращает результат типа ParseResult , который в конечном итоге может являться наследником одного из двух базовых типов – Success или Failure .

Разумеется, библиотека комбинаторов парсеров значительно более обширна, чем показано в листинге 6, в частности, реализация функций

и rep не совсем тривиальна. Тем не менее, теперь у вас должно быть базовое представление о том, как работают комбинаторы. Они «комбинируют» парсеры в целях повышения уровня абстракции и реализации нужной схемы синтаксического анализа (отсюда и название – «комбинаторы»).

Это все?

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

Листинг 7. Первый же тест сигнализирует об ошибке

Данный тест завершится с ошибкой, потому что метод parseAll нашего парсера не возвращает экземпляр case-класса Number (этого можно было ожидать, так как нигде внутри парсера не задана связь между case-классами и грамматическими правилами). Он также не возвращает набор текстовых элементов (токенов) или чисел.

Вместо этого парсер возвращает результат типа Parsers.ParseResult , который, как вы теперь знаете, может быть либо экземпляром Parsers.Success (в случае успешного завершения разбора), либо экземпляром классов Parsers.NoSuccess , Parsers.Failure или Parsers.Error (все они говорят о том, что в процессе разбора возникли ошибки).

Если разбор завершился без ошибок, то результат можно получить при помощи метода get класса ParseResult . Таким образом, метод Calc.parse достаточно слегка видоизменить, как показано в листинге 8. После этого тест должен выполняться успешно.

Листинг 8. Второй вариант парсера на основе БНФ

Все работает! Так ведь?

К сожалению, не совсем. Запустив тесты, вы увидите, что парсер по-прежнему возвращает результаты не тех типов, которые должны присутствовать в AST ( еxpr и т. д.). Вместо этого возвращаемые значения представляют собой списки, строки и тому подобное. Разумеется, можно попросту анализировать эти результаты еще раз, преобразуя в экземпляры expr и производя последующие вычисления, но должен быть более легкий путь.

Легкий путь, разумеется, есть, но для того чтобы его найти, нам понадобится глубже разобраться с тем, как комбинаторы могут возвращать «нестандартные» результаты (т. е. не строки и не списки). Другими словами, как заставить парсер создавать объекты пользовательских типов, в нашем случае тех, которые используются в AST. Именно этим мы займемся в следующей статье, где рассмотрим основы реализации комбинаторов. Вы узнаете, как преобразовывать входной текст в AST для последующего вычисления значения (а также для компиляции).

Об этой серии

Тед Ньювард погружается в язык программирования Scala и берет вас с собой. В этой новой серии developerWorks вы узнаете, вокруг чего поднята такая шумиха и увидите некоторые лингвистические возможности Scala в действии. Код Scala и код Java будут показаны в сравнении, если это важно, но (как скоро выяснится) многие вещи в Scala не могут быть напрямую соотнесены с чем-либо таким, что вам знакомо из Java-программирования, но в этом и заключается основное очарование Scala! В конце концов, если Java-код способен это сделать, зачем же утруждать себя изучением Scala?

Заключение

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

Если вам не терпится узнать, как решается проблема с типами, то прочитайте документацию Scaladoc по методу ^^ либо раздел книги «Программирование на Scala«, посвященный комбинаторам парсеров. Имейте в виду, что язык, рассматриваемый в нашей статье, немного более сложен, чем примеры, приводимые в этих ресурсах.

Разумеется, можно напрямую работать со строками и списками и полностью игнорировать AST. В частности, можно заново анализировать строки и списки, преобразуя в узлы AST. Однако какой во всем этом смысл, когда столько удобных средств можно найти в библиотеке комбинаторов?

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

Похожие темы

  • Оригинал статьи: «The busy Java developer’s guide to Scala: Building a calculator, Part 2» (Ted Neward, developerWorks, октябрь 2008 г.). (EN)
  • Начните чтение этой серии с первой статьи под заголовком: «Путеводитель по Scala для Java-разработчиков: Функциональное программирование вместо объектно-ориентированного» (Тед Ньювард, developerWorks, январь 2008 г.), в которой приводится обзор языка Scala и, в частности, его функционального подхода к задачам параллельной обработки данных. Другими статьями этой серии являются следующие:
    • «Классная работа» (февраль 2008 г.), описывающая синтаксис и семантику Scala.
    • «Не зацикливайтесь!» (март 2008 г.), в которой подробно рассматриваются конструкции для логического ветвления в Scala.
    • «Признаки и поведение объектов» (апрель 2008 г.), в которой рассказывается об использовании Java-интерфейсов в Scala. (EN)
    • «Наследование реализации» (май 2008 г.), объясняющая полиморфизм в Scala. (EN)
    • «Коллекции» (июнь 2008 г.), которая посвящена кортежам, массивам и спискам. (EN)
    • «Пакеты и модификаторы доступа» (июль 2008 г.), в которой описываются средства работы с пакетами и модификаторами доступа, а также объясняется механизм apply . (EN)
    • «Создание калькулятора, часть 1» (август 2008 г.), в которой содержится первая часть материала о создании DSL. (EN)
  • Прочитайте статью «Функциональное программирование на Java» (Абхиджит Белапуркар, Abhijit Belapurkar, developerWorks, июль 2004 г.), в которой обсуждаются преимущества и возможности применения функционального программирования с точки зрения разработчика Java. (EN)
  • Ознакомьтесь со статьей «Scala в примерах» (Мартин Одерски, Martin Odersky, декабрь 2007 г.), в которой приводится короткое введение в Scala, изобилующее примерами (формат PDF). (EN)
  • Прочитайте книгу Программирование на Scala (Мартин Одерски, Лекс Спун, Lex Spoon и Билл Веннерс; сигнальный экземпляр вышел в свет в Artima в феврале 2008 г.) – первое подробное введение в Scala, написанное в соавторстве с Биллом Веннерсом. (EN)
  • Загрузите Scala и начните ее изучение с этой серии. (EN)
  • Загрузите SUnit — набор классов пакета scala.testing, входящего в стандартный дистрибутив Scala. (EN)
  • Сотни статей по всем аспектам программирования на Java можно найти на сайте developerWorks в разделе Технология Java.

Комментарии

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

«Студент правильно сделает, если возьмётся за изучение Scala». Руководитель Scala-разработки Evolution Gaming — о редком языке программирования

Два месяца назад разработчик игр для онлайн-казино Evolution Gaming открыл представительство в Минске. Минский офис стал четвёртым R&D-центром компании, наряду с Ригой, Таллином и Амстердамом. Несколько лет назад разработку в Evolution Gaming перевели на Scala.

О преимуществах и перспективах этого довольно редкого языка программирования dev.by поговорил с руководителем Scala-разработки компании Юрисом Крикисом.

«Изменять код можно уверенно»

Как вы изучали Scala и когда начали кодить на нём?

Примерно в 2012 году я начал искать для себя «улучшенную версию Java», который тогда слабо развивался. Начинал с Groovy — мне нравилось, но казалось, что можно лучше. И тогда я стал изучать Scala. Поначалу язык выглядел слишком сложным. В то время экосистема Scala не была такой зрелой, как сейчас: например, плохо работала подсветка кода в IDE. Но мне вовремя попались хорошие курсы Мартина Одерского на Coursera, они помогли понять язык. Scala мне понравился, и я начал активно его использовать.

Мастер Йода рекомендует:  Методы RegExp и String Javascript
Чем именно вам понравился язык?

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

Насколько Scala популярен сегодня?

Мне кажется, он более популярен, чем многие другие языки, которые больше на слуху. Конечно, Scala-разработчиков меньше, чем программистов, работающих на Java, JavaScript или Python. Но Scala более распространён в индустрии, чем те же Go, Rust или Kotlin.

Для каких областей разработки Scala подходит лучше всего?

Scala — язык общего назначения (general purpose), который работает на платформе JVM, как и Java. На нём можно делать всё то же, что и на Java, плюс есть пара особых моментов. Во-первых, это фреймворк Akka, который используется и в Scala, и в Java, но в Scala удобнее. Фреймворк хорош для разработки распределённых приложений, которые обеспечивают transaction processing (обработку транзакций), например, в финансовой или банковской сфере, а также в приложениях со ставками на спорт и онлайн-казино — то, чем, собственно, и занимается Evolution Gaming. Во-вторых, Scala используют для Apache Spark — инструмента процессинга данных.

Для широкой публики Scala известен как язык разработки некоторых популярных соцсетей: Twitter, Linkedin, WhatsApp…

Думаю, что для соцсетей важен тот самый transaction processing. Фреймворк Akka подходит для быстрого отправления сообщений разным пользователям. Scala — это язык, который работает на JVM, что раскрывает возможности функционального программирования. Код, написанный на Scala, легче поддерживать. Кроме того, Scala «дружит» с Java-библиотеками и вашим существующим Java-кодом.

«Если разработчик знает Scala — это показатель того, что он улучшает своё умение писать код»

Можете назвать страны или регионы, где Scala знают и активно на нём кодят?

У меня нет статистики по географическому распределению таких компаний и программистов. Мы, например, успешно находим Scala-разработчиков в России, Беларуси, Украине и Латвии. Довольно много и эффективно Scala используют в Швейцарии, Нидерландах и Великобритании — в этих странах работают большие компании, которые используют Scala.

И всё же Scala — не самый распространенный язык программирования. Как считаете, почему?

А как определить уровень популярности? Есть разная статистика, но в топах сейчас, пожалуй, JavaScript и Java. У Java очень долгая история (язык появился в 1995 году), а чем старше язык, тем больше людей его знают и используют. На другой язык и платформу переключаться всегда сложно. Хотя, допустим, перейти с Java на Scala сравнительно просто, только это долгий процесс. В Evolution Gaming мы переводили классический Java-стэк на новый язык в течение нескольких лет.

А недостатки у Scala есть? Возможно, тот факт, что язык не очень популярен в среде разработчиков, накладывает какие-то ограничения?

Конечно, находить Java-разработчиков гораздо проще. Хотя бы потому, что их намного больше. Но если разработчик знает Scala — это своеобразный показатель того, что он развивает навыки функционального программирования, улучшает своё умение писать код и станет для нас предпочтительным кандидатом.

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

Scala позиционируется как язык, который объединяет в себе черты объектно-ориентированного и функционального программирования. Это действительно так?

В Scala есть возможности как объектно-ориентированного программирования, так и функционального. Но на практике вовсе не обязательно все эти возможности использовать в одном проекте. Всё зависит от задач и стиля Scala, в котором вы работаете. Вы можете использовать Scala в стиле «улучшенный Java», а можете и в стиле функционального программирования.

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

Какие перспективы у Scala?

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

Scala был разработан в швейцарской Федеральной политехнической школе в начале 2000-х. А кто сейчас занимается улучшением языка?

Тот, кто язык придумал, — Мартин Одерски. Он презентовал Scala 3.0 и продолжает активно работать над развитием и популяризацией языка. Кроме того, существует open-source-коммьюнити по разработке Scala, а также организации, которые работают над Scala-фреймворками: например, Typelevel Scala, Lightbend или Zio.

Вы уже несколько раз упомянули Akka-фреймворк…

Akka-фреймворк — это сочетание разных библиотек для написания распределённых сервисов (distributed services). Он включает в себя и Akka-кластер, и Akka Persistence. Мы используем Akka-кластер, например, для горизонтального скейлинга наших микросервисов и для коммуникации между ними. С помощью Akka Persistence мы храним данные о состоянии системы, используя Event Sourcing- и CQRS-подходы. Мы даже разработали свой Akka Persistence-плагин, который решает проблемы storage-ивентов.

«Код на Scala получается более качественным и с меньшим количеством багов, чем на Java»

В какой момент вы решили, что разработка в Evolution Gaming должна перейти на Scala?

В 2013 году мы начали использовать Scala в юнит-тестах. В компании были энтузиасты, которые хотели использовать наиболее эффективный и передовой способ разработки. Ведь код на Scala получается более качественным и с меньшим количеством багов, чем на Java, его проще поддерживать. Плюс Scala позволяет использовать Akka-систему естественнее, чем в случае с Java.

Scala понравилась разработчикам, и мы начали переводить свои приложения на новый язык и фреймворки: c Java и фреймворков Hazelcast, Hibernate, Spring и JSP на Scala и Akka, Akka Persistence. Разбили монолитное приложение на микросервисы, которые общаются между собой, используя Kafka. То есть изменения касались не только языка — изменялась вся экосистема. При этом интеграцию надо было провести постепенно, чтобы система не переставала успешно и безотказно работать в продакшне и обновляться.

В итоге все микросервисы, которые поддерживают наши игровые приложения, перешли с Java на Scala, и на этом языке разрабатывается бэкенд всех новых микросервисов. Ещё остались некоторые побочные системы, которые работают на Java, и одна система на Groovy, но мы и их переводим на новый язык.

Для игровой индустрии Scala — подходящий выбор?

В бэкенд-разработке — конечно. Фронтенд наших игр сделан на TypeScript, тоже типизированном языке, и React and Redux фреймворках. Scala.js используем для некоторых систем внутреннего пользования — например, системы планирования для наших сотрудников, которые ведут игры (их более 4600).

Насколько сложно найти хорошего Scala-разработчика в Беларуси и Латвии?

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

Когда вы находите Java-разработчика, сколько времени уходит на его обучение новому языку?

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

По сравнению с другими языками, Scala овладеть сложнее?

Вопрос ведь не только про язык. Вопрос про парадигму, библиотеки. Начать не сложно, но чтобы стать экспертом в языке, придётся проделать очень большую работу. Скорее всего, это будет сложнее, чем овладеть Java на таком же уровне.

Имеет смысл начинающему разработчику, даже студенту, браться за изучение Scala или выгоднее овладеть более распространёнными языками?

Мы пару раз находили выпускников университетов, которым уже нравился Scala. Важным языком функционального программирования является Haskell, и в университетах его учат. Но вакансий Haskell-разработчиков меньше, и в продакшне его используют реже. Хотя язык прекрасный — Scala много позаимствовал у Haskell. Думаю, студент правильно сделает, если возьмётся за изучение Scala.

Владение не самым популярным языком программирования как-то отражается на зарплате разработчика?

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

Насколько сегодня Evolution Gaming испытывает потребность в Scala-разработчиках?

Активно набираем людей в Минске, Риге и Таллине. Благодаря высокому качеству образования в Минске много хороших разработчиков, а у нашей компании интересные проекты и технический стэк. ​

Познакомиться с компанией поближе можно 3 сентября на Open Doors Day в SPACE.

Путеводитель по Scala для Java-разработчиков: Часть 2. Cоздание калькулятора

В последнее время все большую популярность приобретают предметно-ориентированные языки (domain-specific languages — DSL). Вследствие этого одной из наиболее важных характеристик функциональных языков является их применимость для создания DSL. В этой статье Тед Ньювард рассказывает о синтаксическом анализе выражений, написанных на ранее рассмотренном DSL, и преобразовании их в AST для последующей интерпретации (AST и интерпретатор выражений были описаны в предыдущей статье). В статье будут также продемонстрированы возможности функциональных языков для создания «внешних» DSL. Синтаксический анализ текста и создание древовидной структуры данных будет реализовано при помощи так называемых комбинаторов парсеров , которые представляют собой стандартную библиотеку Scala, разработанную специально для этих целей.

На настоящий момент ситуация выглядит следующим образом: в процессе создания DSL (в нашем случае — простого языка арифметических выражений) мы определили структуру AST со следующими типами вершин:

  • бинарные операторы сложения, вычитания, умножения и деления;
  • унарный оператор смены знака (отрицания);
  • числовые константы.

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

При этом код интерпретатора выглядит следующим образом (листинг 1).

Те из вас, кто прочитал предыдущую статью, помнят, что в качестве упражнения было предложено усовершенствовать оптимизацию, чтобы упрощение выражений выполнялось на произвольном уровне дерева, а не только на верхнем, как в листинге 1. Наиболее простой вариант был предложен Лексом Спуном (Lex Spoon). Он заключается в том, что функция simplify сначала вызывается рекурсивно для каждого из операндо, а затем выполняется упрощение самой операции на более высоком уровне (листинг 2).

На данный момент перед нами стоит вторая часть задачи по созданию DSL — создание программного модуля, преобразующего выражение, заданное в текстовом виде, в AST. Этот процесс известен под именем » синтаксический разбор » и состоит из нескольких фаз: разбиение на токены , лексический анализ и синтаксический анализ.

Исторически существуют два способа создания программ для синтаксического разбора (парсеров):

  • вручную;
  • при помощи автоматизированных средств (генераторов парсеров).

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

Альтернативой служит использование генераторов парсеров. Исторически наибольшую популярность получили два генератора — lex (генератор лексических анализаторов) и yacc (Yet Another Compiler Compiler — генератор синтаксических анализаторов). Благодаря этим программам разработчикам парсеров не приходится создавать их вручную, достаточно передать на вход lex специальное описание лексики языка, после чего тот автоматически создаст самую низкоуровневую часть будущего парсера. Затем полученный лексический анализатор вместе с файлом, описывающим грамматические правила языка, передаются на вход yacc, который генерирует код парсера (грамматика языка определяет, в частности, список ключевых слов, правила описания блоков кода и т. д.).

Этот процесс подробно описан в учебниках по теории вычислительных наук, поэтому мы не будем углубляться в детали конечных автоматов и парсеров LARL- и LR-грамматик. Те из вас, кому интересны эти подробности, могут обратиться к статьям и книгам по данной тематике.

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

Для понимания целей создания и возможностей комбинаторов парсеров желательно определенное знакомство с таким способом описания грамматики языка, как форма Бэкуса-Наура (Backus-Naur Form — БНФ). Например, наш язык арифметических выражений можно описать в БНФ следующим образом (листинг 3).

В БНФ элемент в левой части каждого выражения задает имя для набора различных вариантов входных данных. Элементы в правой части представляют собой константы и выражения языка, а также их сочетания.

Одним из преимуществ описания языка в БНФ является то, что от БНФ легче перейти к комбинаторам парсеров в Scala. Обратите внимание на листинг 4, в котором показана упрощенная форма БНФ из листинга 3.

В этой записи фигурные скобки ( <> ) обозначают возможность повторения (ноль или более раз), а вертикальная черта ( / , также известная как «конвейерный символ») — отношение типа «или». Таким образом, в листинге 4 записано, что выражение factor может быть либо выражением типа floatingPointNumber (определение этого правила опущено), либо выражением типа expr , заключенным в круглые скобки.

Грамматику, записанную в такой форме, легко трансформировать в парсер на Scala (листинг 5).

Преобразование фактически заключается в замене грамматических правил в БНФ на синтаксические конструкции комбинаторов: пробелы меняются на методы

, обозначающие последовательность, повторы — на метод rep , а «или» по-прежнему представляются в виде / . Строковые константы также остаются без изменений.

Своим удобством данный подход частично обязан двум вещам: стандартному базовому классу JavaTokenParsers , от которого унаследован класс парсера, и комбинатору floatingPointNumber . Первый, в свою очередь, является наследником других классов, которые полезны в случае, если анализируемый язык не столь хорошо сочетается с синтаксическими конструкциями в Java. Второй же берет на себя детали разбора десятичной записи чисел с плавающей точкой.

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

Для того чтобы понять принципы работы показанного выше парсера, необходимо ненадолго погрузиться в вопросы реализации комбинаторов. По своей природе каждый «парсер» представляет собой функцию (или case-класс), которая получает некоторые входные данные и возвращает объект-парсер. Например, комбинаторы самого нижнего уровня базируются на простых парсерах, которые принимают на вход объекты, считывающие элементы из входного потока (экземпляры Reader ) и возвращающие объекты, обладающие некоторый высокоуровневой семантикой (экземпляры Parser ). Пример показан в листинге 6.

Таким образом, Elem является абстрактным типом для представления любого вида входных данных, которые можно подвергнуть синтаксическому разбору. Как правило, таковыми являются текстовые строки или потоки. Тип Input представляет собой Elem , заключенный внутрь scala.util.parsing.input.Reader (квадратные скобки означают, что Reader является параметризованным типом данных, они играют роль, аналогичную угловым скобкам в C++ или Java). Наконец, Parser типа T — это такой тип данных, который принимает на вход экземпляр Input и возвращает результат типа ParseResult , который в конечном итоге может являться наследником одного из двух базовых типов — Success или Failure .

Разумеется, библиотека комбинаторов парсеров значительно более обширна, чем показано в листинге 6, в частности, реализация функций

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

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

Данный тест завершится с ошибкой, потому что метод parseAll нашего парсера не возвращает экземпляр case-класса Number (этого можно было ожидать, так как нигде внутри парсера не задана связь между case-классами и грамматическими правилами). Он также не возвращает набор текстовых элементов (токенов) или чисел.


Вместо этого парсер возвращает результат типа Parsers.ParseResult , который, как вы теперь знаете, может быть либо экземпляром Parsers.Success (в случае успешного завершения разбора), либо экземпляром классов Parsers.NoSuccess , Parsers.Failure или Parsers.Error (все они говорят о том, что в процессе разбора возникли ошибки).

Если разбор завершился без ошибок, то результат можно получить при помощи метода get класса ParseResult . Таким образом, метод Calc.parse достаточно слегка видоизменить, как показано в листинге 8. После этого тест должен выполняться успешно.

Все работает! Так ведь?

К сожалению, не совсем. Запустив тесты, вы увидите, что парсер по-прежнему возвращает результаты не тех типов, которые должны присутствовать в AST ( еxpr и т. д.). Вместо этого возвращаемые значения представляют собой списки, строки и тому подобное. Разумеется, можно попросту анализировать эти результаты еще раз, преобразуя в экземпляры expr и производя последующие вычисления, но должен быть более легкий путь.

Легкий путь, разумеется, есть, но для того чтобы его найти, нам понадобится глубже разобраться с тем, как комбинаторы могут возвращать «нестандартные» результаты (т. е. не строки и не списки). Другими словами, как заставить парсер создавать объекты пользовательских типов, в нашем случае тех, которые используются в AST. Именно этим мы займемся в следующей статье, где рассмотрим основы реализации комбинаторов. Вы узнаете, как преобразовывать входной текст в AST для последующего вычисления значения (а также для компиляции).

Тед Ньювард погружается в язык программирования Scala и берет вас с собой. В этой новой серии developerWorks вы узнаете, вокруг чего поднята такая шумиха и увидите некоторые лингвистические возможности Scala в действии. Код Scala и код Java будут показаны в сравнении, если это важно, но (как скоро выяснится) многие вещи в Scala не могут быть напрямую соотнесены с чем-либо таким, что вам знакомо из Java-программирования, но в этом и заключается основное очарование Scala! В конце концов, если Java-код способен это сделать, зачем же утруждать себя изучением Scala?

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

Если вам не терпится узнать, как решается проблема с типами, то прочитайте документацию Scaladoc по методу ^^ либо раздел книги » Программирование на Scala «, посвященный комбинаторам парсеров. Имейте в виду, что язык, рассматриваемый в нашей статье, немного более сложен, чем примеры, приводимые в этих ресурсах.

Разумеется, можно напрямую работать со строками и списками и полностью игнорировать AST. В частности, можно заново анализировать строки и списки, преобразуя в узлы AST. Однако какой во всем этом смысл, когда столько удобных средств можно найти в библиотеке комбинаторов?

Интерпретатор «по-функциональному»: Haskell, OCaml и Scala на практике

Содержание статьи

Давненько мы не касались темы функционального программирования. успел ли ты соскучиться? Уверен, что нет, но деваться некуда :), ведь сегодня я настроен показать тебе реальную силу функционального подхода. Заодно мы рассмотрим то общее, что объединяет функциональные языки. В этой статье мы попробуем написать простейший интерпретатор сразу на трех языках: Haskell, OCaml и Scala.

Введение

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

По устройству компиляторов существует масса книг («Книга с драконом» и многие другие), которые очень подробно описывают процесс синтаксического анализа, оптимизации и кодогенерации.
Есть много классических инструментов, разработанных так давно, что если в качестве аналогии рассмотреть оружие, то утилиты lex и yacc сродни первобытным копьям и каменным топорам. В этом смысле инструменты, которые предоставляют Haskell и другие функциональные языки, может быть, и не выглядят как огнестрельное оружие, но представляют собой аналоги по меньшей мере катаны и вакидзаси в мире проектирования языков программирования.

В этой статье весь код будет написан на трех языках, чтобы увидеть сходства и различия в использовании тех или иных инструментов программирования. Если про Haskell и Scala слышали, наверное, все, то OCaml на данный момент кажется довольно экзотическим языком. Но это совершенно не значит, что он чем-то плох. Это очень мощное расширение языка ML, который был придуман еще очень давно (в начале семидесятых). Сейчас лямбда-функции уже мейнстрим, а вот вывод типов только начинает набирать популярность — но если подумать, что он уже был в ML, то становится немного страшно. Даже компания Microsoft выпустила язык F#, который очень похож на OCaml, только работает он в окружении .NET. На самом деле довольно много языков программирования были изначально реализованы на OCaml. Список можно найти здесь, тем более что это просто прекрасный источник знаний о данном языке. Следует отметить, что на OCaml (и на ML вообще) написано несколько систем автоматического доказательства теорем.

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

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

Язык программирования Scala был придуман сравнительно недавно и уже очень популярен среди разработчиков для JVM. Этот язык сочетает в себе многие прекрасные особенности функциональных и объектно ориентированных языков. На Scala написано множество библиотек и интернет-сервисов. Надо сказать, что богатые возможности этого языка также ведут и к проблемам. В каком-то смысле Scala напоминает C++, где одно и то же можно сделать двадцатью различными способами и в результате код, который написан просто для того, чтобы он работал, совершенно невозможно читать или тем более повторно использовать.

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

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

Сложные типы данных

В каждом языке высокого уровня, помимо базовых типов, таких как целые числа и числа с плавающей запятой, должны быть предусмотрены средства комбинирования, то есть механизмы, позволяющие создавать из более простых типов более сложные. В языке Lisp это списки, в языке С — массивы, структуры и объединения, в Java — классы. На самом деле все это сводится к двум операциям:

  • декартово произведение;
  • размеченное объединение.

В функциональных языках декартово произведение обычно представлено с помощью кортежа, в ООП это классы. Размеченное объединение в функциональных языках играет существенную роль, так как позволяет различать объекты в рамках одного типа, но с разной структурой. В объектно ориентированных языках это реализовано с помощью наследования. В некоторых функциональных языках, таких как OCaml и Haskell, — при помощи так называемых алгебраических типов. На самом деле такое название не должно пугать. Вот как это выглядит в OCaml:

Здесь у нас есть один тип данных, для которого существуют два конструктора с принципиально разным набором параметров. Причем против конструктора можно использовать сопоставление с образцом, как это сделано в функции area , которая вычисляет площадь соответствующей фигуры. Из особенностей языка здесь следует отметить явное указание на то, что мы имеем дело с декартовым произведением в конструкторе Rect . OCaml также интересен тем, что для целых чисел и чисел с плавающей запятой он имеет синтаксически различные наборы операций, для вещественных чисел соответствующая операция заканчивается точкой: *.

Разумеется, во всех языках этот вопрос решается разными функциями, потому что это две принципиально различные операции, однако почти во всех языках имеется механизм, который делает эти операции полиморфными (в широком смысле этого слова). Это может быть как чисто синтаксическое решение (как в Java), так и более сложное, как, например, классы типов в Haskell (механизм, сходный с полиморфизмом в ООП).

Код на Haskell поразительно схож с кодом на OCaml, и это связано с тем, что язык ML, по сути, их общий предок. В Haskell довольно много синтаксического сахара: к примеру, с помощью data можно создавать декартово произведение, не используя кортеж напрямую. Еще одна особенность в том, что в Haskell можно выносить тип функции в виде аннотации за пределы определения самой функции (точнее, строчкой выше). Это очень удобно. Здесь сказано, что функция area является функцией из Shape в Double .

По правде говоря, это можно сделать и в OCaml, однако для этого придется создать заголовочный файл, что не всегда удобно. Несмотря на то что OCaml и Haskell сами выводят тип функции, рекомендую указывать тип в аннотации в случае Haskell и напрямую в OCaml let area (s: shape): float = . , чтобы облегчить чтение и отладку кода.

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

Однако все можно сделать куда более похожим на функциональное программирование, используя следующий подход:

Здесь также используется сопоставление с образцом. Трейты (trait) в Scala очень напоминают интерфейсы в Java (можно было использовать и абстрактные классы, как в предыдущем примере, — это, скорее, дело вкуса), а в нашем конкретном случае обеспечивают имитацию алгебраического типа, так как классы Circle и Rect , с одной стороны, будут иметь тип Shape , с другой — ключевое слово sealed делает наше размеченное объединение «закрытым справа».

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

Абстрактные синтаксические деревья

Теперь, когда с функциональным подходом к сложным типам данных все понятно, попробуем разобраться с такой полезной штукой, как абстрактное синтаксическое дерево (AST). Начнем с совсем тривиального примера: положим, что нам нужно написать простейший интерпретатор математических выражений. Чтобы еще упростить пример, будем считать, что нам хватит простейших операций, таких как сложение, вычитание и умножение. Суть абстрактного синтаксического дерева очень проста — это абстракция S-выражения (из языка Lisp) для соответствующей части программы (в нашем случае просто математического выражения). Таким образом, абстрактное синтаксическое дерево для выражения 10 + 2 * 5 можно записать как (+ 10 (* 2 5)) . На языке OCaml код будет выглядеть следующим образом:

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Изучение программирования путем Скалы?

Конкретно по Scala:

С точки зрения Scala, на мой взгляд самый оптимальный вариант это пройти курсы по Scala на coursera.org. Но ниже будут примеры и ссылки и других источников для иучения программирования на Scala..

cousera.org:
Специализация по Scala — один из фундаментальных источников знаний о программировании на Scala. В составе — видео лекции, доп. материалы, вопросы на закрепление, задания на программирование, форум для вопросов и обсуждения (все опционально). В принципе можно вместо SICP сразу начинать со специализации на coursera.org, в частности первый курс в специализации как раз таки проектировался Мартином в стиле SICP (по крайней мере до создания специализации программа курса и задания были в духе SICP).


Интерактивные упажнения по Scala от 47 Degrees (на данный момент 6 курсов std lib, cats, shapeless, doobie, scalacheck, FP in Scala)
https://www.scala-exercises.org

www.scalakoans.org — небольшие уроки для изучения

Big data university (с уклоном в Data Science и Big Data):

Если вас больше интересует не интерактивный стиль обучения, а вдумчивое чтение, то можно посоветовать следующие книги:

На русском,к сожалению, есть только одна книга:
Scala для нетерпеливых
но для новичка в программировании она скорее всего не подойдет.

также в 2020 вышла обновленная версия книги от Martin Odersky, Lex Spoon и Bill Venners, которая покрывает все вопросы связанные со Scala (859 стр), в том числе все нововведения на текущий момент (вплоть до Scala 2.12). Эту книгу можно также читать если вы новичок в программировании. Это наверно самый фундаментальный источник для изучения Scala:
Programming in Scala, Third Edition

После ознакомления с основами Scala для углубления именно функциональных аспектов программирования на Scala можно прочитать замечательную книгу от Paul Chiusano и Rúnar Bjarnason:
Functional Programming in Scala

Для изучения практических основ проектирования современных приложений с использованием возможностей Scala и ее экосистемы, с применением функциональной парадигмы, рекомендую отличную книгу от Debasish Ghosh
Functional and Reactive Domain Modeling

Вообще по Scala и различным инструментам из экосистемы Scala сейчас много разных книг, так что при желании можно найти подходящую по уровню и интересам, стоит также обратить внимание на подборку книг на сайте lightbend (бывшая TypeSafe)

Алгоритмы
На счет алгоритмов, про которые вы упоминали, можете посмотреть прекрасный набор классических алгоритомов и структур данных, реализованных на Scala в чисто функциональном стиле:
scalacaster от @vkostyukov(Twitter, Finagle)
или
scalgos от @pathikrit
——————————————

Остальное
Ninety-Nine Scala Problems — набор небольших упражнений (с ответами) для изучения Scala

Type-Level Programming in Scala — серия постов из десяти частей на тему продвинутого использования возможностей системы типов Scala от Rúnar Bjarnason (соавтор Functional Programming in Scala) и Mark Harrah (разработчик sbt)

Scala Best Practices — колеекция хорощих практик программирования на Scala от Alexandru Nedelcu

JVM! Advanced Scala и Функциональное Программирование

Rock the JVM! Advanced Scala and Functional Programming

Станьте программистом Scala высшего уровня, вы cможете легко работать с Spark, Akka, Monix и с любым инструментом Scala! В этом курсе мы изучим приемы, которыми пользуются лучшие 1% разработчиков Scala. Вы сами напишите более 5000 строк кода Scala, и станете рок-звездой.

Этот курс предназначен для программистов Scala, которым необходимо разрабатывать сложные масштабируемые системы, или настраивать существующие инструменты (например, Spark) или писать свои собственные.

Почему Scala:

  • это самый популярный язык на основе виртуальной машины Java — количество заданий Scala резко возросло, но спрос еще выше
  • он предлагает значительно более высокую заработную плату (> 20%), чем позиции Java того же уровня
  • лучшим разработчикам Scala платят в 2 раза, 10 раз, и даже больше!
  • это невероятно весело — однажды попробовав Scala, вы никогда не захотите вернуться назад
  • у вас не будет проблем с использованием некоторых новейших технологий — Spark, Akka и других

Мне нравится добираться до сути и добиваться цели. Этот курс

  • разбирает передовые концепции Scala на критические части, которые вам нужны
  • выбирает самые важные идеи и разделяет их на простые, но важные
  • последовательность идей таким образом, что «щелкает» и имеет смысл на протяжении всего процесса обучения
  • применяет все в живом коде

Конечные выгоды еще намного больше:

  • совершенно новый уровень мастерства с языком Scala
  • значительный рост заработной платы при переходе на Scala — возврат инвестиций для этого курса> 100 раз за первый год!
  • больше приятной работы — Scala это весело!
  • навыки, ориентированные на будущее — вы сможете работать с некоторыми передовыми технологиями (Spark, Akka и тд.)

Этот курс предназначен для опытных программистов, имеющих опыт работы с Scala и с функциональным программированием на уровне курса Rock the JVM Scala для начинающих. Я уже предполагаю твердое понимание общих основ программирования. Если вы никогда не программировали раньше, этот курс не для вас.

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

Я считаю, что и теория, и практика важны. Вот почему вы получите лекции с примерами кода, демонстрациями и заданиями из реального кода Scala, а также дополнительными ресурсами, инструкциями, упражнениями и решениями. В конце курса вы напишите тысячи строк Scala.

Я видел, что мои ученики наиболее успешны — а мои лучшие ученики работают в компаниях класса Google — когда ими руководят, но им не говорят, что делать. У меня есть задания, которые ждут вас, где я даю свое (самоуверенное) руководство, но в остальном свобода экспериментировать и улучшать ваш код Scala.

Определенно не в последнюю очередь, мои студенты наиболее успешны, когда им весело по пути!

Так что присоединяйтесь ко мне в этом курсе и давайте раскачиваем JVM!

Интерпретатор «по-функциональному»: Haskell, OCaml и Scala на практике

Содержание статьи

Давненько мы не касались темы функционального программирования. успел ли ты соскучиться? Уверен, что нет, но деваться некуда :), ведь сегодня я настроен показать тебе реальную силу функционального подхода. Заодно мы рассмотрим то общее, что объединяет функциональные языки. В этой статье мы попробуем написать простейший интерпретатор сразу на трех языках: Haskell, OCaml и Scala.

Введение

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

По устройству компиляторов существует масса книг («Книга с драконом» и многие другие), которые очень подробно описывают процесс синтаксического анализа, оптимизации и кодогенерации.
Есть много классических инструментов, разработанных так давно, что если в качестве аналогии рассмотреть оружие, то утилиты lex и yacc сродни первобытным копьям и каменным топорам. В этом смысле инструменты, которые предоставляют Haskell и другие функциональные языки, может быть, и не выглядят как огнестрельное оружие, но представляют собой аналоги по меньшей мере катаны и вакидзаси в мире проектирования языков программирования.

В этой статье весь код будет написан на трех языках, чтобы увидеть сходства и различия в использовании тех или иных инструментов программирования. Если про Haskell и Scala слышали, наверное, все, то OCaml на данный момент кажется довольно экзотическим языком. Но это совершенно не значит, что он чем-то плох. Это очень мощное расширение языка ML, который был придуман еще очень давно (в начале семидесятых). Сейчас лямбда-функции уже мейнстрим, а вот вывод типов только начинает набирать популярность — но если подумать, что он уже был в ML, то становится немного страшно. Даже компания Microsoft выпустила язык F#, который очень похож на OCaml, только работает он в окружении .NET. На самом деле довольно много языков программирования были изначально реализованы на OCaml. Список можно найти здесь, тем более что это просто прекрасный источник знаний о данном языке. Следует отметить, что на OCaml (и на ML вообще) написано несколько систем автоматического доказательства теорем.

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

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

Язык программирования Scala был придуман сравнительно недавно и уже очень популярен среди разработчиков для JVM. Этот язык сочетает в себе многие прекрасные особенности функциональных и объектно ориентированных языков. На Scala написано множество библиотек и интернет-сервисов. Надо сказать, что богатые возможности этого языка также ведут и к проблемам. В каком-то смысле Scala напоминает C++, где одно и то же можно сделать двадцатью различными способами и в результате код, который написан просто для того, чтобы он работал, совершенно невозможно читать или тем более повторно использовать.

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

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

Сложные типы данных

В каждом языке высокого уровня, помимо базовых типов, таких как целые числа и числа с плавающей запятой, должны быть предусмотрены средства комбинирования, то есть механизмы, позволяющие создавать из более простых типов более сложные. В языке Lisp это списки, в языке С — массивы, структуры и объединения, в Java — классы. На самом деле все это сводится к двум операциям:

  • декартово произведение;
  • размеченное объединение.

В функциональных языках декартово произведение обычно представлено с помощью кортежа, в ООП это классы. Размеченное объединение в функциональных языках играет существенную роль, так как позволяет различать объекты в рамках одного типа, но с разной структурой. В объектно ориентированных языках это реализовано с помощью наследования. В некоторых функциональных языках, таких как OCaml и Haskell, — при помощи так называемых алгебраических типов. На самом деле такое название не должно пугать. Вот как это выглядит в OCaml:

Здесь у нас есть один тип данных, для которого существуют два конструктора с принципиально разным набором параметров. Причем против конструктора можно использовать сопоставление с образцом, как это сделано в функции area , которая вычисляет площадь соответствующей фигуры. Из особенностей языка здесь следует отметить явное указание на то, что мы имеем дело с декартовым произведением в конструкторе Rect . OCaml также интересен тем, что для целых чисел и чисел с плавающей запятой он имеет синтаксически различные наборы операций, для вещественных чисел соответствующая операция заканчивается точкой: *.

Разумеется, во всех языках этот вопрос решается разными функциями, потому что это две принципиально различные операции, однако почти во всех языках имеется механизм, который делает эти операции полиморфными (в широком смысле этого слова). Это может быть как чисто синтаксическое решение (как в Java), так и более сложное, как, например, классы типов в Haskell (механизм, сходный с полиморфизмом в ООП).

Код на Haskell поразительно схож с кодом на OCaml, и это связано с тем, что язык ML, по сути, их общий предок. В Haskell довольно много синтаксического сахара: к примеру, с помощью data можно создавать декартово произведение, не используя кортеж напрямую. Еще одна особенность в том, что в Haskell можно выносить тип функции в виде аннотации за пределы определения самой функции (точнее, строчкой выше). Это очень удобно. Здесь сказано, что функция area является функцией из Shape в Double .

По правде говоря, это можно сделать и в OCaml, однако для этого придется создать заголовочный файл, что не всегда удобно. Несмотря на то что OCaml и Haskell сами выводят тип функции, рекомендую указывать тип в аннотации в случае Haskell и напрямую в OCaml let area (s: shape): float = . , чтобы облегчить чтение и отладку кода.

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

Однако все можно сделать куда более похожим на функциональное программирование, используя следующий подход:

Здесь также используется сопоставление с образцом. Трейты (trait) в Scala очень напоминают интерфейсы в Java (можно было использовать и абстрактные классы, как в предыдущем примере, — это, скорее, дело вкуса), а в нашем конкретном случае обеспечивают имитацию алгебраического типа, так как классы Circle и Rect , с одной стороны, будут иметь тип Shape , с другой — ключевое слово sealed делает наше размеченное объединение «закрытым справа».

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

Абстрактные синтаксические деревья

Теперь, когда с функциональным подходом к сложным типам данных все понятно, попробуем разобраться с такой полезной штукой, как абстрактное синтаксическое дерево (AST). Начнем с совсем тривиального примера: положим, что нам нужно написать простейший интерпретатор математических выражений. Чтобы еще упростить пример, будем считать, что нам хватит простейших операций, таких как сложение, вычитание и умножение. Суть абстрактного синтаксического дерева очень проста — это абстракция S-выражения (из языка Lisp) для соответствующей части программы (в нашем случае просто математического выражения). Таким образом, абстрактное синтаксическое дерево для выражения 10 + 2 * 5 можно записать как (+ 10 (* 2 5)) . На языке OCaml код будет выглядеть следующим образом:

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Основы функционального программирования с примерами на Scala — часть 2

Основы функционального программирования и языка Scala

Функциональное программирование. Язык Scala.

Базовые типы языка Scala. Базовые управляющие конструкции.

Классы и объекты. Определение методов. Бинарные операторы. Унарные операторы. Абстрактные классы. Трейты.

Система пакетов. Модификаторы доступа.

Немодифицируемые коллекции. List, Vector, Set, Range, Map. Пары.

Функции как объекты языка. Локальные определения функций. Передача функций как параметры и возврат в качестве результата.

Операции высшего порядка над коллекциями. Цикл for.

Параметризованные типы. Ковариантные и контравариантные параметры. Верхние и нижние границы типов.

Модифицируемые коллекции. ListBuffer, ArrayBuffer, Map.

Сопоставление с образцом. Case classes. Частично-применимые функции. Каррирование.

Работа со строками. Регулярные выражения.

Технологии разработки Scala-приложений

Модульное тестирование. ScalaTest.

Использование Maven для сборки приложения.

Генерация документации ScalaDoc.

Графические библиотеки AWT и Swing

Базовые компоненты Swing.

Продвинутые возможности языка

Неявные параметры. Неявные преобразования

Метапрограммирование и построение DSL

Использование неявных преобразований для расширения встроенных объектов.

Использование неявных параметров для передачи контекста.

Создание новых конструкций языка за счет использования функций высшего порядка.

Обработка внешних DSL. Комбинаторные парсеры.

Многопоточное и распределенное программирование на Scala

Многопоточное программирование. Синхронизация. Взаимные блокировки и борьба с ними.

Примитивы синхронизации пакета java.util.concurrent.

Безблокировочные алгоритмы. Атомарные операции. Пакет java.util.concurrent.atomic.

Программирование на основе сообщений. Акторы. Библиотека Akka.

«Студент правильно сделает, если возьмётся за изучение Scala». Руководитель Scala-разработки Evolution Gaming — о редком языке программирования

Два месяца назад разработчик игр для онлайн-казино Evolution Gaming открыл представительство в Минске. Минский офис стал четвёртым R&D-центром компании, наряду с Ригой, Таллином и Амстердамом. Несколько лет назад разработку в Evolution Gaming перевели на Scala.

О преимуществах и перспективах этого довольно редкого языка программирования dev.by поговорил с руководителем Scala-разработки компании Юрисом Крикисом.

«Изменять код можно уверенно»

Как вы изучали Scala и когда начали кодить на нём?

Примерно в 2012 году я начал искать для себя «улучшенную версию Java», который тогда слабо развивался. Начинал с Groovy — мне нравилось, но казалось, что можно лучше. И тогда я стал изучать Scala. Поначалу язык выглядел слишком сложным. В то время экосистема Scala не была такой зрелой, как сейчас: например, плохо работала подсветка кода в IDE. Но мне вовремя попались хорошие курсы Мартина Одерского на Coursera, они помогли понять язык. Scala мне понравился, и я начал активно его использовать.

Чем именно вам понравился язык?

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

Насколько Scala популярен сегодня?

Мне кажется, он более популярен, чем многие другие языки, которые больше на слуху. Конечно, Scala-разработчиков меньше, чем программистов, работающих на Java, JavaScript или Python. Но Scala более распространён в индустрии, чем те же Go, Rust или Kotlin.

Для каких областей разработки Scala подходит лучше всего?

Scala — язык общего назначения (general purpose), который работает на платформе JVM, как и Java. На нём можно делать всё то же, что и на Java, плюс есть пара особых моментов. Во-первых, это фреймворк Akka, который используется и в Scala, и в Java, но в Scala удобнее. Фреймворк хорош для разработки распределённых приложений, которые обеспечивают transaction processing (обработку транзакций), например, в финансовой или банковской сфере, а также в приложениях со ставками на спорт и онлайн-казино — то, чем, собственно, и занимается Evolution Gaming. Во-вторых, Scala используют для Apache Spark — инструмента процессинга данных.

Для широкой публики Scala известен как язык разработки некоторых популярных соцсетей: Twitter, Linkedin, WhatsApp…

Думаю, что для соцсетей важен тот самый transaction processing. Фреймворк Akka подходит для быстрого отправления сообщений разным пользователям. Scala — это язык, который работает на JVM, что раскрывает возможности функционального программирования. Код, написанный на Scala, легче поддерживать. Кроме того, Scala «дружит» с Java-библиотеками и вашим существующим Java-кодом.

«Если разработчик знает Scala — это показатель того, что он улучшает своё умение писать код»

Можете назвать страны или регионы, где Scala знают и активно на нём кодят?

У меня нет статистики по географическому распределению таких компаний и программистов. Мы, например, успешно находим Scala-разработчиков в России, Беларуси, Украине и Латвии. Довольно много и эффективно Scala используют в Швейцарии, Нидерландах и Великобритании — в этих странах работают большие компании, которые используют Scala.

И всё же Scala — не самый распространенный язык программирования. Как считаете, почему?

А как определить уровень популярности? Есть разная статистика, но в топах сейчас, пожалуй, JavaScript и Java. У Java очень долгая история (язык появился в 1995 году), а чем старше язык, тем больше людей его знают и используют. На другой язык и платформу переключаться всегда сложно. Хотя, допустим, перейти с Java на Scala сравнительно просто, только это долгий процесс. В Evolution Gaming мы переводили классический Java-стэк на новый язык в течение нескольких лет.

А недостатки у Scala есть? Возможно, тот факт, что язык не очень популярен в среде разработчиков, накладывает какие-то ограничения?

Конечно, находить Java-разработчиков гораздо проще. Хотя бы потому, что их намного больше. Но если разработчик знает Scala — это своеобразный показатель того, что он развивает навыки функционального программирования, улучшает своё умение писать код и станет для нас предпочтительным кандидатом.

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

Scala позиционируется как язык, который объединяет в себе черты объектно-ориентированного и функционального программирования. Это действительно так?

В Scala есть возможности как объектно-ориентированного программирования, так и функционального. Но на практике вовсе не обязательно все эти возможности использовать в одном проекте. Всё зависит от задач и стиля Scala, в котором вы работаете. Вы можете использовать Scala в стиле «улучшенный Java», а можете и в стиле функционального программирования.

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

Какие перспективы у Scala?

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

Scala был разработан в швейцарской Федеральной политехнической школе в начале 2000-х. А кто сейчас занимается улучшением языка?

Тот, кто язык придумал, — Мартин Одерски. Он презентовал Scala 3.0 и продолжает активно работать над развитием и популяризацией языка. Кроме того, существует open-source-коммьюнити по разработке Scala, а также организации, которые работают над Scala-фреймворками: например, Typelevel Scala, Lightbend или Zio.

Вы уже несколько раз упомянули Akka-фреймворк…

Akka-фреймворк — это сочетание разных библиотек для написания распределённых сервисов (distributed services). Он включает в себя и Akka-кластер, и Akka Persistence. Мы используем Akka-кластер, например, для горизонтального скейлинга наших микросервисов и для коммуникации между ними. С помощью Akka Persistence мы храним данные о состоянии системы, используя Event Sourcing- и CQRS-подходы. Мы даже разработали свой Akka Persistence-плагин, который решает проблемы storage-ивентов.

«Код на Scala получается более качественным и с меньшим количеством багов, чем на Java»

В какой момент вы решили, что разработка в Evolution Gaming должна перейти на Scala?

В 2013 году мы начали использовать Scala в юнит-тестах. В компании были энтузиасты, которые хотели использовать наиболее эффективный и передовой способ разработки. Ведь код на Scala получается более качественным и с меньшим количеством багов, чем на Java, его проще поддерживать. Плюс Scala позволяет использовать Akka-систему естественнее, чем в случае с Java.

Scala понравилась разработчикам, и мы начали переводить свои приложения на новый язык и фреймворки: c Java и фреймворков Hazelcast, Hibernate, Spring и JSP на Scala и Akka, Akka Persistence. Разбили монолитное приложение на микросервисы, которые общаются между собой, используя Kafka. То есть изменения касались не только языка — изменялась вся экосистема. При этом интеграцию надо было провести постепенно, чтобы система не переставала успешно и безотказно работать в продакшне и обновляться.

В итоге все микросервисы, которые поддерживают наши игровые приложения, перешли с Java на Scala, и на этом языке разрабатывается бэкенд всех новых микросервисов. Ещё остались некоторые побочные системы, которые работают на Java, и одна система на Groovy, но мы и их переводим на новый язык.

Для игровой индустрии Scala — подходящий выбор?

В бэкенд-разработке — конечно. Фронтенд наших игр сделан на TypeScript, тоже типизированном языке, и React and Redux фреймворках. Scala.js используем для некоторых систем внутреннего пользования — например, системы планирования для наших сотрудников, которые ведут игры (их более 4600).

Насколько сложно найти хорошего Scala-разработчика в Беларуси и Латвии?

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

Когда вы находите Java-разработчика, сколько времени уходит на его обучение новому языку?

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

По сравнению с другими языками, Scala овладеть сложнее?

Вопрос ведь не только про язык. Вопрос про парадигму, библиотеки. Начать не сложно, но чтобы стать экспертом в языке, придётся проделать очень большую работу. Скорее всего, это будет сложнее, чем овладеть Java на таком же уровне.

Имеет смысл начинающему разработчику, даже студенту, браться за изучение Scala или выгоднее овладеть более распространёнными языками?

Мы пару раз находили выпускников университетов, которым уже нравился Scala. Важным языком функционального программирования является Haskell, и в университетах его учат. Но вакансий Haskell-разработчиков меньше, и в продакшне его используют реже. Хотя язык прекрасный — Scala много позаимствовал у Haskell. Думаю, студент правильно сделает, если возьмётся за изучение Scala.

Владение не самым популярным языком программирования как-то отражается на зарплате разработчика?

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

Насколько сегодня Evolution Gaming испытывает потребность в Scala-разработчиках?

Активно набираем людей в Минске, Риге и Таллине. Благодаря высокому качеству образования в Минске много хороших разработчиков, а у нашей компании интересные проекты и технический стэк. ​

Познакомиться с компанией поближе можно 3 сентября на Open Doors Day в SPACE.

Мастер Йода рекомендует:  Официальные фьючерсы на Bitcoin впервые представлены на американской опционной бирже
Добавить комментарий