Почему СкалаВлад Патрышев |
Аннотация: Статья знакомит читателя с языком программирования Скала, который сочетает возможности функционального и объектно-ориентированного программирования.
This article introduces the Scala programming language, which combines functional and object-oriented programming features.
1 Почему Скала
Языки приходят и уходят; некоторые остаются. Скала — не первый язык, комбинирующий объектное ориентирование с функциональным программированием, но он появился в нужный момент, когда Джава уже всем откровенно поднадоела. Теперь Скала вполне готова постепенно заменить Джаву, работая на той же виртуальной машине и предоставляя доступ к тем же джавным библиотекам.
Мартин Одерски в начале 2000-х разрабатывал параллельно Скалу и дженерики для Джавы. Джавная версия дженериков оказалась несколько недоделанной, если не сказать — ошибочной (невозможно указать вариантность параметров [11]). В Скале этот недостаток устранён: вариантность присутствует, и недоуменные вопросы «насчёт дженериков», так часто возникающие при программировании на Джаве, сами собой отпадают.
Вариантность параметра типа — это указание на то, что происходит при замене типа на подтип: получим ли мы подтип или нет. Например, немодифицируемый список строк можно использовать как подтип немодифицируемого списка объектов (ковариантность); для модифицируемых такое явление не наблюдается (инвариантность); бывают и случаи, когда отношение подтипов меняет направление (контравариантность).
Так как Скала работает на привычной JVM, и даже компилируется в Джаву, использовать её можно практически везде, где есть Джава версии не ниже 5 (т. е., не на Blackberry). Переход с Джавы на Скалу можно начать с небольшого ознакомления с языком; нет необходимости всё глубоко изучать и всё радикально переписывать. Инфраструктура может оставаться той же самой; например, можно использовать тот же Tomcat, но часть сервлетов будет на Скале.
2 Особенности языка
Скала — язык, на котором можно писать в объектно-ориентированном стиле и в функциональном — по вкусу. Если вы привыкли программировать на Джаве, то при переходе на Скалу поменяется не так уж много: в-основном, выкинете лишние операции и будете наслаждаться новыми удобствами. Если вы любите программировать на Хаскеле, то обнаружите, что, по крайней мере внешне, многое из того, к чему вы привыкли, можно вполне разборчиво выразить на Скале. Система типов в Скале мощнее, чем в Хаскеле-98 (благодаря наличию higher-ranking types, т. е. дженериков высшего порядка).
3 С точки зрения Джавы
Итак, на Скале можно писать как на обычной Джаве со слегка изменённым синтаксисом. Всё есть объект: нет примитивных типов. (Читатель, конечно, тут же возмутится — а как же производительность? Стандартный ответ на это таков: JIT всё перемелет в эффективный код. К тому же, начиная с версии 2.8.0, используется специализация с помощью примитивных типов; за подробностями отсылаю к источникам.)
Все джавные классы доступны из Скалы, и наоборот. Единственная сложность состоит в том, что коллекции в Джаве и в Скале очень сильно отличаются, и чтобы работать с джавной коллекцией на Скале так, как это принято на Скале, эту коллекцию надо конвертировать, обычно неявным способом. Но если вы будете писать код «как на Джаве», то можно и не конвертировать.
Например, в компании «KaChing» Скала используетcя вперемешку с Джавой, и неявные преобразования из скальных коллекций в джавные и обратно — обычное дело. Добавим ещё, что в Скале имеются такие же аннотации, что и в Джаве, что даёт возможность использовать и Guice, и Hibernate.
В Скале нет джавных массивов, а есть вместо него класс Array
; поэтому квадратные скобки можно использовать для других целей. В Скале квадратные скобки используются для параметров типа, вместо угловых.
Аналогом джавного понятия интерфейса в Скале является типаж (trait
); типаж может содержать частичную реализацию, и работать в качестве абстрактного класса — хотя класс может наследовать сразу несколько типажей. Это не ведёт к проблемам, типичным для множественного наследования, т. к. речь не идёт о классах, а о типажах.
Метод может принадлежать классу или объекту; функции также определяются внутри класса или объекта. Если вам нужна функция, не привязанная к экземпляру класса, то её можно определить в статическом объекте. По традиции Скалы, к классу добавляется так называемый объект-компаньон (companion object) c тем же именем, что и класс:
Эти же объекты-компаньоны можно использовать в качестве фабрик.
Оператор «==
» в Скале соответствует, буквально, джавному .
equals
()
для ненулевых ссылок и равенству для null
; для буквального (ссылочного) равенства в Скале нужно использовать метод eq
, определённый для класса AnyRef
.
Тип переменной определяется через двоеточие: var
s
:
String
.
Функции и методы определяются через def
:
Вот как в Скале выглядят шаблоны (generics):
(Здесь мы извлекаем список по ключу "
today
"
, затем берём пятый элемент списка.)
В отличие от Джавы, шаблоны могут быть высших порядков (см. [9]):
Такая конструкция невозможна в Джаве (cм., например, [8]). Невозможно написать вот такой интерфейс:
Циклы можно писать практически как в Джаве; цикл while
ничем не отличается, а цикл, известный под названием «foreach», выглядит так:
Можно писать и более вычурные циклы:
Это, конечно, эквивалентно следующему:
Долгожданная новинка — замыкания (closures). Конечно, в Джаве тоже можно написать что-то вроде замыканий, используя интерфейсы и анонимные классы, но на практике всё это выливается в совершенно нечитаемый код. На Скале замыкания выглядят гораздо более естественно:
Разумеется, джавный стиль программирования — это только верхушка айсберга; им удобно пользоваться, если вы только переходите с Джавы на Скалу. По мере освоения вы обнаружите, что Скала способна на очень многое, практически невыразимое на Джаве.
Две новых особенности языка (относительно Джавы) удивят и кого-то порадуют, а кого-то и нет: неявные преобразования и миксины.
В Джаве мы привыкли, что int
приводится к long
и что в определённом контексте любой объект приводится к String
— а в Скале этими преобразованиями можно управлять. В качестве примера возьмём строку, и превратим её, с помощью неявного преобразования, в объект, очень похожий на массив:
Теперь можно писать val
c
= "
This
is
a
string
"(2)
— строка будет неявно преобразована в RandomAccessSeq
[
Char
]
, а значение c
будет вычислено и равно '
i
'
.
Ниже приводятся другие примеры неявных преобразований.
Теперь мы можем обращаться со строками, как если бы это были массивы:
Миксины в Скале реализованы с помощью типажей. Миксины позволяют добавлять к классу функциональность, определённую не в непосредственном суперклассе, а где-то ещё; в Скале типаж может содержать определения методов. Вот простой пример:
Здесь методы debug
()
и ping
наследуются из типажа Programmer
.
4 С точки зрения функциональных языков
Несмотря на то, что Скала бегает на JVM и предоставляет все возможности «объектного программирования», это вполне функциональный язык. Функция может передаваться в качестве параметра, может быть значением переменной, может быть анонимным литералом, может возвращаться:
В последних двух примерах мы взяли списки и применили к каждому элементу функцию, получая новый список. В Скале имеется масса традиционных для ФП операций над списками, например:
К спискам, в частности, применимы свёртки:
Есть кортежи (ака n-ки); есть аналог data types, есть карринг — многое из того, ради чего люди переходят на Хаскель. Но так как язык этот в принципе-то императивный, то при вводе-выводе можно формально обойтись без монад.
Пример кортежа:
Пример алгебраического типа:
case
class
— особый вид класса, в частности, пригодный для использования в конструкции switch. В таком классе много чего хорошего; казалось бы, почему не сделать все классы case-классами? Проблема в том, что сравнение с образцом становится проблематичным при наличии наследования; поэтому у таких классов наследование ограничено.
Пример карринга:
Здесь sum определяется как функция, которая принимает целочисленный аргумент (x
) и возвращает функцию, которая принимает целочисленный аргумент (y
). Если мы напишем sum
(5)
, то это и будет называться «карринг».
Классы типов (type classes) реализованы с помощью типажей и полиморфизма, например:
Выше, в разделе «С точки зрения Джавы», этот же пример приведён в качестве образца шаблона высшего порядка… на одну и ту же вещь можно по-разному смотреть.
Вместо переменных в Скале используется val
— такие значения определяются один раз и не меняются. Можно использовать и var
— тогда это будет обычная переменная, как в Джаве. val
можно объявить ленивой:
В этом случае значение вычисляется только в момент, когда оно требуется, в выражении или в качестве неленивого параметра.
В качестве альтернативы ленивой переменной можно использовать функцию без параметров:
Между ленивой переменной и функцией без параметров имеется существенное различие: ленивая переменная вычисляется один раз, а функция — каждый раз.
Ещё Скала замечательна сопоставлением с образцом (pattern matching; подробнее на с. ??) и частичными функциями.
Как задаётся частичная функция? Через сопоставление с образцом:
Это эквивалентно следующему коду:
(Выражение x
::
y
::
z
::
Nil
эквивалентно выражению List
(
x
,
y
,
z
)
).
Частичная функция может использоваться по-разному:
Естественно, возникает вопрос: как же так, язык объектно-ориентированный; есть методы у объектов — как это всё сочетается с наличием обычных функций, не методов? Ну вот взять, например,
Здесь очевидно, что f1
— метод, а f2
— функция.
Какая разница между f1
и f2
? Можно ли написать val
f3
=
f1
? Нет, нельзя. Чтобы превратить метод класса в полноправную функцию (т. е., экземпляр типа Function
), нужно, формально говоря, добавить подчёркивание после пробела:
Эта запись буквально означает, что мы определяем новое значение, имеющее тип «функция», и эта функция имеет те же параметры, что и метод f1
, и тот же тип результата; и для вычисления f3
нужно подставить её параметры в f1
. Так как f1
вполне может использовать члены класса, в котором она определена, то мы получаем замыкание (closure). f3
можно передавать в качестве параметра или возвращать; это теперь самостоятельная сущность.
Обычные списки в Cкале неленивы: если функция возвращает список, то все его элементы должны быть вычислены. Но есть ленивые потоки (Stream
). Вот, например, направляем ленивый поток целых чисел в решето Эратосфена и получаем ленивый поток простых чисел:
Очень легко создавать встроенные предметно-ориентированные языки (DSL), см. например, [5]:
Как ни странно это выглядит, но всё это — Скала, а не макросы и не текст для парсера. Как и в предыдущих примерах, здесь мы пользуемся удобствами языка.
5 Сопоставление с образцом: подробности
Одна из приятнейших особенностей Скалы — сопоставление с образцом. После Джавы — сплошное удовольствие.
Сначала простой пример. Сопоставляем значения, как в обычном )switch
/
case
:
На самом деле Джава с 2007 года обещает конструкцию switch и для строк… Скоро будет.
В Скале с помощью образцов можно сопоставлять не только значения, но и алгебраические типы данных, особенно учитывая, что они допускают деконструкцию:
Cопоставление с образцом, вместе с регулярными выражениями и с экстракторами, даёт удобную возможность разбирать текст:
Это, конечно, если язык описывается регулярным выражением. Если же грамматика посложнее, то нужно писать парсер-комбинатор, как в нижеследующем примере.
6 Встроенный XML
Для определённого рода приложений (ну, скажем, для веба) тот факт, что XML является в Скале частью языка, освобождает от необходимости писать массу рутинного ненужного кода. Вот пример:
7 Примеры кода
Любимый хаскельцами квиксорт, правда, в неленивом варианте (т. е., не имеющий практического смысла), т. к. списки не ленивы:
Здесь мы берём список, и, если он пуст, возвращаем его же, а иначе берём первый элемент; сортируем элементы, которые меньше этого первого; сортируем элементы, которые больше этого первого; наконец, соединяем всё в нужном порядке.
Хитрый трюк с неявным преобразованием, позволяющий определять операторы на существующих классах:
Что тут у нас получилось? Мы вычисляем 42!
; т. к. оператор !
определён на значениях типа Fact
, то мы пробуем неявное преобразование Integer
в Fact
— а так как такое преобразование определено (int2fact
), то его и используем. После чего к результату применяем оператор !
— и получаем в результате BigInt
; его и печатаем в println
()
.
8 Тестирование
Для юниттестов имеется два пакета: scalatest
([12]) и scalacheck
. Вот типичный пример теста для scalatest
:
В версии для scalacheck
это может выглядеть примерно так:
scalacheck
для тестирования генерирует порядочное количество различных стеков, проверяя указанные свойства. Какие именно стеки сгенерируются, зависит от интеллекта (искусственного) — необходимый набор тестовых примеров выводится из условий и из определения.
Но можно писать и по-простому, как в JUnit:
9 Готовые продукты на Скале
Скалу можно скачать с http://www.scala-lang.org/downloads; этот дистрибутив включает в себя компилятор, библиотеки, repl-интерпретатор, и документацию. Очень рекомендую прекрасный плагин для Скалы в IntelliJ: этот плагин делает всё, что надо — рефакторинг, юниттесты, есть и отладчик. Версия 2.8 плагина для Eclipse тоже вполне работоспособна; существует также плагин для Netbeans.
Ну и наконец, Lift, ради которого уже стоит срочно браться за Скалу, так как этот веб-фреймворк лет на десять обогнал большинство остальных.
Lift, фреймворк для создания веб-приложений, разумеется, опирается на всё, что было достигнуто в вебе за последние десять лет, но он идёт дальше. Прежде всего, это строгое разделение компонент MVC; затем, каждая форма уникальна, и невозможно передать одну и ту же информацию дважды или вернуться на ту же страницу: каждый экземпляр формы содержит уникальную метку. Так как Скала может содержать XML, не нужны отдельные JSP, это всё пишется прямо в коде — но презентация отделена от логики структурой приложения.
Вот что сказал Мартин Одерски, дизайнер Скалы: «Lift — единственный фреймворк из появившихся за последние годы, предлагающий свежие и оригинальные решения для разработки веб-приложений. Это не какое-нибудь незначительное улучшение предыдущих решений; он задаёт новые стандарты. Если вы разрабатываете веб-приложения, вы должны изучить Lift. Если вы даже не используете его ежедневно, он изменит ваш подход к веб-приложениям.»
Образец HTML и соответствующего кода на Скале:
10 Где что на нём делают
Самый сейчас передовой пример — это foursquare.com, небольшой нью-йоркский стартап, чья социальная сеть следит за вашим местоположением и даёт возможность пригласить всех в понравившийся вам ресторан и отследить, кто где находится. За полгода весь код переписан на ScalaLift.
Yammer: Artie, служба рассылки сообщений, написана на Скале.
Twitter: порядочное количество серверного кода переписано на Скалу, а остальное пока на Ruby on Rails.
LinkedIn: там было порядочное количество скальщиков, но, пока писалась эта статья, они все, похоже, уже разбежались — как и из Гугла до того.
KaChing: желающие пишут на Скале, хотя основная масса кода на Джаве; проблем совместимости нет.
Есть ещё пара стартапов в Сан-Франциско, пишущих исключительно на Скале; но если вы пойдёте на Dice, то обнаружите с десяток контор, где требуется Скала, даже в таких экзотических местах, как штат Теннесси.
11 Рекомендуемая литература
David Pollack «Beginning Scala» [10] — пособие для начинающих. Если вы серьёзно хотите изучить и использовать Скалу, вряд ли вам нужна эта книга. Для серьёзного изучения лучше всего подойдёт
Martin Oderski, Lexi Spoon, Bill Venners «Programming in Scala» [13]. Большая книга, но её не обязательно читать вдоль, можно и поперёк. [14] — перевод главы из этой книги.
Dean Wampler, Alex Payne «Programming Scala» [1]. В этой книге меньше страниц, но больше Скалы, различных пикантных деталей. Хороша в качестве «второй книги» по этому языку.
[18], [16], [17] — три статьи Антона Панасенко, дельно описывающие те или иные аспекты языка.
[15] — русский перевод статьи «Обзор языка Scala» Мартина Одерски и множества других.
[9] — проект ScalaZ, Тони Морриса (Tony Morris). Читается как стихи Лорки в оригинале.
Ну и, наконец, для настоящих героев — книга Gregory Meredith «Pro Scala: Monadic Design Patterns for the Web» [7]. Мередит — большой теоретик, и читать его непросто. Но стоит того: если вы научитесь рассуждать на его уровне, то уже ничего не страшно.
Derek Chen-Becker, Tyler Weir, Marius Danciu «The Definitive Guide to Lift» [6]. Небольшая книга по ScalaLift. Так как Lift развивается быстрее, чем печатаются книги, то лучше информацию черпать из следующих источников: [4], [3], [2].
Список литературы
- [1]
- Dean Wampler Alex Payne. Programming Scala: Scalability = Functional Programming + Objects, http://programming-scala.labs.oreilly.com/. O’Reilly, 2009.
- [2]
- Derek Chen-Becker. Проект на Lift — <<Мелочь в Кармане>>. Проект, http://github.com/tjweir/pocketchangeapp/tree/master/PocketChange.
- [3]
- Marius Danciu David Pollak, Derek Chen-Becker and Tyler Weir. Starting with Lift. Веб-страница, http://old.liftweb.net/docs/getting_started/mod_master.html.
- [4]
- David Pollack et al. Getting Started With Lift. Веб-сайт, http://liftweb.net/getting_started.
- [5]
- Debashish Ghosh. Designing Internal DSLs in Scala. Блог, http://debasishg.blogspot.com/2008/05/designing-internal-dsls-in-scala.html.
- [6]
- Tyler Weir Marius Danciu, Derek Chen-Becker. The Definitive Guide to Lift: A Scala-based Web Framework, http://www.amazon.com/Definitive-Guide-Lift-Scala-based-Framework/dp/1430224215. Apress, 2007.
- [7]
- Gregory Meredith. Pro Scala: Monadic Design Patterns for the Web, http://www.amazon.com/Pro-Scala-Monadic-Design-Patterns/dp/143022844X. Apress, 2010?
- [8]
- JP Moresmaugh. Java and higher order generics. Блог, http://jpmoresmau.blogspot.com/2007/12/java-and-higher-order-generics.html.
- [9]
- Tony Morris. Проект scalaz. Проект в Google Code, http://code.google.com/p/scalaz/.
- [10]
- David Pollack. Beginning Scala, http://www.amazon.com/Beginning-Scala-David-Pollak/dp/1430219890. Apress, 2009.
- [11]
- David Rupp. Java generics broken? we report, you decide. Блог, http://davidrupp.blogspot.com/2008/01/java-generics-broken-we-report-you.html.
- [12]
- Bill Venners. Scalatest. Проект, http://www.scalatest.org/.
- [13]
- Bill Venners, Martin Odersky, and Lexi Spoon. Programming in Scala: A Comprehensive Step-by-step Guide, http://www.amazon.com/Programming-Scala-Comprehensive-Step---step/dp/0981531601/. Artima, 2008.
- [14]
- Лекси Спун, Бил Веннерс, Мартин Одерски. Первые шаги в Scala. RSDN, http://www.rsdn.ru/article/scala/scala.xml.
- [15]
- Мартин Одерски и другие. Обзор языка программирования Scala. RSDN, http://www.rsdn.ru/article/philosophy/Scala.xml.
- [16]
- Антон Панасенко. Scala: Actors (part 2). Блог, http://blog.apanasenko.me/2009/12/scala-actors-part-2/.
- [17]
- Антон Панасенко. Scala: Functional Language (part 3). Блог, http://blog.apanasenko.me/2009/12/scala-functional-language-part-3/.
- [18]
- Антон Панасенко. Scala: введение в мир FL JVM (part 1). Блог, http://blog.apanasenko.me/2009/12/scala-fl-jvm-part-1/.
Этот документ был получен из LATEX при помощи HEVEA