Scala ( / ˈ s k ɑː l ɑː / SKAH -lah ) [7] [8] — это сильный статически типизированный язык программирования высокого уровня общего назначения , поддерживающий как объектно-ориентированное, так и функциональное программирование . Разработанный с целью быть кратким, [9] многие из проектных решений Scala направлены на устранение критики Java . [6]
Исходный код Scala может быть скомпилирован в байт-код Java и запущен на виртуальной машине Java (JVM). Scala также может быть транспилирован в JavaScript для запуска в браузере или скомпилирован непосредственно в собственный исполняемый файл. При запуске на JVM Scala обеспечивает языковое взаимодействие с Java, так что библиотеки, написанные на любом языке, могут быть напрямую связаны в коде Scala или Java. [10] Как и Java, Scala является объектно-ориентированным и использует синтаксис, называемый фигурными скобками , который похож на язык C. Начиная с Scala 3, есть также возможность использовать правило off-side (отступ) для структурирования блоков , и его использование рекомендуется. Мартин Одерски сказал, что это оказалось самым продуктивным изменением, введенным в Scala 3. [11]
В отличие от Java, Scala имеет много особенностей функциональных языков программирования (таких как Scheme , Standard ML и Haskell ), включая карринг , неизменяемость , ленивые вычисления и сопоставление с образцом . Он также имеет расширенную систему типов, поддерживающую алгебраические типы данных , ковариацию и контравариантность , типы более высокого порядка (но не типы более высокого ранга ), анонимные типы , перегрузку операторов , необязательные параметры , именованные параметры , необработанные строки и экспериментальную версию алгебраических эффектов, которая может рассматриваться как более мощная версия проверяемых исключений Java . [12]
Название Scala представляет собой сочетание слов scalable (масштабируемый) и language (язык) , что означает, что он разработан с возможностью роста в соответствии с потребностями пользователей. [13]
Разработка Scala началась в 2001 году в Федеральной политехнической школе Лозанны (EPFL) (в Лозанне , Швейцария ) Мартином Одерски . Она стала продолжением работы над Funnel, языком программирования, объединяющим идеи функционального программирования и сетей Петри . [14] Ранее Одерски работал над Generic Java и javac , компилятором Java от Sun. [14]
После внутреннего релиза в конце 2003 года Scala была выпущена публично в начале 2004 года на платформе Java , [15] [6] [14] [16] Вторая версия (v2.0) появилась в марте 2006 года. [6]
17 января 2011 года команда Scala выиграла пятилетний исследовательский грант в размере более 2,3 млн евро от Европейского исследовательского совета . [17] 12 мая 2011 года Одерски и его коллеги запустили Typesafe Inc. (позже переименованную в Lightbend Inc. ), компанию для предоставления коммерческой поддержки, обучения и услуг для Scala. Typesafe получила инвестиции в размере 3 млн долларов в 2011 году от Greylock Partners . [18] [19] [20] [21]
Scala работает на платформе Java ( виртуальная машина Java ) и совместима с существующими программами Java . [15] Поскольку приложения Android обычно пишутся на Java и транслируются из байт-кода Java в байт-код Dalvik (который может быть далее транслирован в машинный код во время установки) при упаковке, совместимость Scala с Java делает ее хорошо подходящей для разработки Android, тем более, когда предпочтителен функциональный подход. [22]
Эталонный дистрибутив программного обеспечения Scala, включая компилятор и библиотеки, выпущен под лицензией Apache . [23]
Scala.js — это компилятор Scala, который компилируется в JavaScript, что позволяет писать программы Scala, которые могут работать в веб-браузерах или Node.js. [ 24] Компилятор, находящийся в разработке с 2013 года, был объявлен не экспериментальным в 2015 году (v0.6). Версия v1.0.0-M1 была выпущена в июне 2018 года, а версия 1.1.1 — в сентябре 2020 года. [25]
Scala Native — это компилятор Scala , нацеленный на инфраструктуру компилятора LLVM для создания исполняемого кода, использующего легковесную управляемую среду выполнения, которая использует сборщик мусора Boehm . Проект возглавляет Денис Шабалин, а его первый релиз, 0.1, состоялся 14 марта 2017 года. Разработка Scala Native началась в 2015 году с целью сделать его быстрее, чем компиляция just-in-time для JVM, исключив начальную компиляцию кода во время выполнения, а также предоставив возможность вызывать собственные процедуры напрямую. [26] [27]
Эталонный компилятор Scala, ориентированный на .NET Framework и его Common Language Runtime, был выпущен в июне 2004 года [14] , но был официально прекращен в 2012 году [28].
Программа Hello World, написанная на Scala 3, имеет следующий вид:
@main def main () = println ( "Привет, мир!" )
В отличие от автономного приложения Hello World для Java , здесь нет объявления классов и ничего не объявлено статическим.
Когда программа сохранена в файле HelloWorld.scala , пользователь компилирует ее с помощью команды:
$ scalac HelloWorld.scala
и запускает его с помощью
$ скала HelloWorld
Это аналогично процессу компиляции и запуска кода Java. Действительно, модель компиляции и выполнения Scala идентична модели Java, что делает ее совместимой с инструментами сборки Java, такими как Apache Ant .
Более короткая версия программы «Hello World» на Scala выглядит так:
println ( "Привет, мир!" )
Scala включает в себя интерактивную оболочку и поддержку скриптов. [29] Сохраненный в файле с именем HelloWorld2.scala
, он может быть запущен как скрипт с помощью команды:
$ scala HelloWorld2.scala
Команды также можно вводить непосредственно в интерпретатор Scala, используя опцию -e :
$ scala -e 'println("Привет, мир!")'
Выражения можно вводить интерактивно в REPL :
$ scala Добро пожаловать в Scala 2.12.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_131). Введите выражения для оценки. Или попробуйте :help.scala> Список(1, 2, 3).map(x => x * x) res0: Список[Int] = Список(1, 4, 9)скала>
Следующий пример показывает различия между синтаксисом Java и Scala. Функция mathFunction принимает целое число, возводит его в квадрат, а затем добавляет кубический корень этого числа к натуральному логарифму этого числа, возвращая результат (т. е. ):
Некоторые синтаксические различия в этом коде:
Int, Double, Boolean
вместо int, double, boolean
.def
.val
(указывает на неизменяемую переменную) или var
(указывает на изменяемую переменную).return
не нужен в функции (хотя и допускается); значением функции обычно является значение последнего выполненного оператора или выражения.(Type) foo
, Scala использует foo.asInstanceOf[Type]
, или специализированную функцию, такую как toDouble
или toInt
.foo()
также могут быть вызваны как just foo
; метод thread.send(signo)
также может быть вызван как just thread send signo
; и метод foo.toString()
также может быть вызван как just foo toString
.Эти синтаксические послабления предназначены для поддержки языков, специфичных для предметной области .
Некоторые другие основные синтаксические различия:
array(i)
а не array[i]
. (Внутри Scala первый вариант раскрывается в array.apply(i), который возвращает ссылку)List[String]
а не как в Java List<String>
.void
в Scala есть реальный класс singleton Unit
(см. ниже).Следующий пример сопоставляет определение классов в Java и Scala.
Приведенный выше код демонстрирует некоторые концептуальные различия между обработкой классов в Java и Scala:
object
вместо class
. Обычно статические переменные и методы помещаются в объект singleton с тем же именем, что и имя класса, который затем называется объектом- компаньоном . [15] (Базовый класс для объекта singleton имеет $
добавленный . Следовательно, для class Foo
with companion object object Foo
, под капотом есть класс, Foo$
содержащий код объекта-компаньона, и один объект этого класса создается с использованием шаблона singleton .)val
или var
поля также определяются с тем же именем и автоматически инициализируются из параметров класса. (Под капотом внешний доступ к публичным полям всегда осуществляется через методы доступа (геттер) и мутатора (сеттер), которые создаются автоматически. Функция доступа имеет то же имя, что и поле, поэтому в приведенном выше примере нет необходимости явно объявлять методы доступа.) Обратите внимание, что можно также объявлять альтернативные конструкторы, как в Java. Код, который будет входить в конструктор по умолчанию (кроме инициализации переменных-членов), идет непосредственно на уровне класса.addPoint
, пример Scala определяет +=
, который затем вызывается с инфиксной нотацией как grid += this
.public
.Scala имеет ту же модель компиляции, что и Java и C# , а именно раздельную компиляцию и динамическую загрузку классов , благодаря чему код Scala может вызывать библиотеки Java.
Эксплуатационные характеристики Scala такие же, как у Java. Компилятор Scala генерирует байт-код, который почти идентичен тому, который генерируется компилятором Java. [15] Фактически, код Scala можно декомпилировать в читаемый код Java, за исключением некоторых операций конструктора. Для виртуальной машины Java (JVM) код Scala и код Java неразличимы. Единственное отличие — одна дополнительная библиотека времени выполнения, scala-library.jar
. [30]
Scala добавляет большое количество функций по сравнению с Java и имеет некоторые фундаментальные отличия в базовой модели выражений и типов, что делает язык теоретически чище и устраняет несколько угловых случаев в Java. С точки зрения Scala это практически важно, поскольку несколько добавленных в Scala функций также доступны в C#.
Как уже упоминалось выше, Scala обладает большой синтаксической гибкостью по сравнению с Java. Ниже приведены некоторые примеры:
"%d apples".format(num)
, и "%d apples" format num
эквивалентны. Фактически, арифметические операторы, такие как +
и, <<
обрабатываются так же, как и любые другие методы, поскольку имена функций могут состоять из последовательностей произвольных символов (за несколькими исключениями, сделанными для таких вещей, как скобки, квадратные скобки и фигурные скобки, которые должны обрабатываться особым образом); единственная специальная обработка, которой подвергаются такие методы с символьными именами, касается обработки приоритета.apply
и update
имеют синтаксические краткие формы. foo()
—где foo
это значение (одиночный объект или экземпляр класса) — является сокращением от foo.apply()
, и foo() = 42
является сокращением от foo.update(42)
. Аналогично, foo(42)
является сокращением от foo.apply(42)
, и foo(4) = 2
является сокращением от foo.update(4, 2)
. Это используется для классов коллекций и распространяется на многие другие случаи, такие как ячейки STM .def foo = 42
) и пустые скобки ( def foo() = 42
). При вызове метода с пустыми скобками скобки можно опустить, что полезно при вызове библиотек Java, которые не знают этого различия, например, используя foo.toString
вместо foo.toString()
. По соглашению метод должен быть определен с пустыми скобками, когда он выполняет побочные эффекты .:
), ожидают аргумент слева и получателя справа. Например, то 4 :: 2 :: Nil
же самое, что и Nil.::(2).::(4)
, первая форма визуально соответствует результату (список с первым элементом 4 и вторым элементом 2).trait FooLike { var bar: Int }
реализация может быть . Место вызова по-прежнему сможет использовать краткий .object Foo extends FooLike { private var x = 0; def bar = x; def bar_=(value: Int) { x = value }} } }
foo.bar = 42
breakable { ... if (...) break() ... }
выглядит так, как будто breakable
это ключевое слово, определенное языком, но на самом деле это просто метод, принимающий аргумент- преобразователь . Методы, принимающие преобразователи или функции, часто помещают их во второй список параметров, что позволяет смешивать синтаксис круглых скобок и фигурных скобок: Vector.fill(4) { math.random }
такой же, как Vector.fill(4)(math.random)
. Вариант с фигурными скобками позволяет выражению охватывать несколько строк.map
, flatMap
и filter
.Сами по себе они могут показаться сомнительными, но в совокупности они служат цели, позволяющей определять в Scala языки, специфичные для предметной области, без необходимости расширения компилятора. Например, специальный синтаксис Erlang для отправки сообщения актору, ie, actor ! message
может быть (и реализуется) в библиотеке Scala без необходимости расширения языка.
Java делает четкое различие между примитивными типами (например, int
и boolean
) и ссылочными типами (любой класс ). Только ссылочные типы являются частью схемы наследования, производной от java.lang.Object
. В Scala все типы наследуются от класса верхнего уровня Any
, непосредственными потомками которого являются AnyVal
(типы значений, такие как Int
и Boolean
) и AnyRef
(ссылочные типы, как в Java). Это означает, что различие Java между примитивными типами и упакованными типами (например, int
против Integer
) отсутствует в Scala; упаковка и распаковка полностью прозрачны для пользователя. Scala 2.10 позволяет пользователю определять новые типы значений.
Вместо циклов Java " foreach " для прохода по итератору, в Scala есть for
-выражения, которые похожи на списочные включения в таких языках, как Haskell , или комбинацию списочных включений и выражений генератора в Python . For-выражения, использующие yield
ключевое слово , позволяют генерировать новую коллекцию путем итерации по существующей, возвращая новую коллекцию того же типа. Они транслируются компилятором в серию вызовов map
, flatMap
и filter
. Если yield
не используется , код приближается к циклу в императивном стиле, транслируясь в foreach
.
Простой пример:
val s = for ( x <- 1 to 25 if x * x > 50 ) yield 2 * x
Результатом его выполнения является следующий вектор:
Vector(16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50)
(Обратите внимание, что выражение 1 to 25
не является специальным синтаксисом. Метод to
скорее определен в стандартной библиотеке Scala как метод расширения целых чисел, использующий технику, известную как неявные преобразования [32], которая позволяет добавлять новые методы к существующим типам.)
Более сложный пример итерации по карте:
// Дана карта, указывающая пользователей Twitter, упомянутых в наборе твитов, // и количество раз, когда каждый пользователь был упомянут, найдите пользователей // на карте известных политиков и верните новую карту, содержащую только // политиков-демократов (как объекты, а не строки). val dem_mentions = for ( mention , times ) <- mentions account <- accounts . get ( mention ) if account . party == "Democratic" yield ( account , times )
Выражение (mention, times) <- mentions
является примером сопоставления с образцом (см. ниже). Итерация по карте возвращает набор кортежей ключ-значение , а сопоставление с образцом позволяет легко деструктурировать кортежи в отдельные переменные для ключа и значения. Аналогично, результат включения также возвращает кортежи ключ-значение, которые автоматически выстраиваются обратно в карту, поскольку исходный объект (из переменной mentions
) является картой. Обратите внимание, что если mentions
вместо этого удерживать список, набор, массив или другую коллекцию кортежей, точно такой же код выше даст новую коллекцию того же типа.
Поддерживая все объектно-ориентированные возможности, доступные в Java (и фактически дополняя их различными способами), Scala также предоставляет большое количество возможностей, которые обычно встречаются только в функциональных языках программирования . Вместе эти возможности позволяют писать программы Scala в почти полностью функциональном стиле, а также позволяют смешивать функциональные и объектно-ориентированные стили.
Вот примеры:
В отличие от C или Java , но подобно таким языкам, как Lisp , Scala не делает различий между операторами и выражениями . Все операторы на самом деле являются выражениями, которые вычисляют некоторое значение. Функции, которые были бы объявлены как возвращающие void
в C или Java, и такие операторы while
логически не возвращают значение, в Scala считаются возвращающими тип Unit
, который является синглтонным типом , с единственным объектом этого типа. Функции и операторы, которые вообще никогда не возвращают (например, throw
оператор или функция, которая всегда завершается нелокально с помощью исключения), логически имеют возвращаемый тип Nothing
, специальный тип, не содержащий объектов; то есть нижний тип , то есть подкласс каждого возможного типа. (Это, в свою очередь, делает тип Nothing
совместимым с каждым типом, позволяя правильно функционировать выводу типа .) [33]
Аналогично, if-then-else
"оператор" на самом деле является выражением, которое производит значение, т. е. результат оценки одной из двух ветвей. Это означает, что такой блок кода может быть вставлен везде, где требуется выражение, устраняя необходимость в тернарном операторе в Scala:
По аналогичным причинам return
операторы не нужны в Scala и, по сути, не приветствуются. Как и в Lisp , последнее выражение в блоке кода является значением этого блока кода, и если блок кода является телом функции, оно будет возвращено функцией.
Чтобы было понятно, что все функции являются выражениями, даже методы, которые возвращают значение, Unit
записываются со знаком равенства.
def printValue ( x : String ): Unit = println ( "Я съел %s" . format ( x ))
или эквивалентно (с выводом типа и пропуском ненужного символа новой строки):
def printValue ( x : String ) = println ( "Я съел %s" format x )
Из-за вывода типа тип переменных, возвращаемых значений функций и многих других выражений обычно можно опустить, поскольку компилятор может вывести его. Примерами являются val x = "foo"
(для неизменяемой константы или неизменяемого объекта ) или var x = 1.5
(для переменной, значение которой может быть впоследствии изменено). Вывод типа в Scala по сути является локальным, в отличие от более глобального алгоритма Хиндли-Милнера , используемого в Haskell, ML и других более чисто функциональных языках. Это делается для упрощения объектно-ориентированного программирования. Результатом является то, что некоторые типы все еще необходимо объявлять (в частности, параметры функций и возвращаемые типы рекурсивных функций ), например
def formatApples ( x : Int ) = "Я съел %d яблок" . format ( x )
или (с возвращаемым типом, объявленным для рекурсивной функции)
def factorial ( x : Int ): Int = если x == 0 , то 1 , иначе x * factorial ( x - 1 )
В Scala функции являются объектами, и существует удобный синтаксис для указания анонимных функций . Примером является выражение x => x < 2
, которое определяет функцию с одним параметром, которая сравнивает свой аргумент, чтобы увидеть, меньше ли он 2. Это эквивалентно форме Lisp (lambda (x) (< x 2))
. Обратите внимание, что ни тип, x
ни тип возвращаемого значения не должны быть явно указаны и, как правило, могут быть выведены путем вывода типа ; но они могут быть явно указаны, например, как (x: Int) => x < 2
или даже (x: Int) => (x < 2): Boolean
.
Анонимные функции ведут себя как истинные замыкания , поскольку они автоматически захватывают любые переменные, которые лексически доступны в среде охватывающей функции. Эти переменные будут доступны даже после возврата охватывающей функции, и в отличие от случая анонимных внутренних классов Java их не нужно объявлять как final. (Такие переменные даже можно изменять, если они изменяемы, и измененное значение будет доступно при следующем вызове анонимной функции.)
Еще более короткая форма анонимной функции использует переменные- заполнители : например, следующая:
list map { x => sqrt(x) }
можно записать более кратко как
list map { sqrt(_) }
или даже
list map sqrt
Scala проводит различие между неизменяемыми и изменяемыми переменными. Изменяемые переменные объявляются с использованием var
ключевого слова , а неизменяемые значения объявляются с использованием val
ключевого слова . Переменная, объявленная с использованием val
ключевого слова , не может быть переназначена так же, как переменная, объявленная с использованием final
ключевого слова , не может быть переназначена в Java. val
s являются только поверхностно неизменяемыми, то есть объект, на который ссылается val, не гарантирует, что сам по себе является неизменяемым.
Однако неизменяемые классы поощряются соглашением, и стандартная библиотека Scala предоставляет богатый набор неизменяемых классов коллекций . Scala предоставляет изменяемые и неизменяемые варианты большинства классов коллекций, и неизменяемая версия всегда используется, если только изменяемая версия явно не импортирована. [34] Неизменяемые варианты — это постоянные структуры данных , которые всегда возвращают обновленную копию старого объекта вместо того, чтобы обновлять старый объект деструктивно на месте. Примером этого являются неизменяемые связанные списки , где добавление элемента в начало списка выполняется путем возврата нового узла списка, состоящего из элемента и ссылки на хвост списка. Добавление элемента в список может быть выполнено только путем добавления всех элементов в старом списке в начало нового списка только с новым элементом. Таким же образом, вставка элемента в середину списка скопирует первую половину списка, но сохранит ссылку на вторую половину списка. Это называется структурным разделением. Это обеспечивает очень простой параллелизм — блокировки не нужны, поскольку никакие общие объекты никогда не изменяются. [35]
Оценка является строгой («жадной») по умолчанию. Другими словами, Scala оценивает выражения, как только они становятся доступны, а не по мере необходимости. Однако можно объявить переменную нестрогой («ленивой») с ключевым lazy
словом, что означает, что код для создания значения переменной не будет оцениваться до тех пор, пока переменная не будет впервые упомянута. Существуют также нестрогие коллекции различных типов (например, тип Stream
, нестрогий связанный список), и любую коллекцию можно сделать нестрогой с помощью view
метода. Нестрогие коллекции обеспечивают хорошее семантическое соответствие таким вещам, как данные, созданные сервером, где оценка кода для создания последующих элементов списка (который, в свою очередь, запускает запрос к серверу, возможно, расположенному где-то еще в Интернете) происходит только тогда, когда элементы действительно необходимы.
Функциональные языки программирования обычно предоставляют оптимизацию хвостового вызова , чтобы обеспечить широкое использование рекурсии без проблем с переполнением стека . Ограничения в байт-коде Java усложняют оптимизацию хвостового вызова на JVM. В общем случае функция, которая вызывает себя с хвостовым вызовом, может быть оптимизирована, но взаимно рекурсивные функции — нет. Трамплины были предложены в качестве обходного пути. [36] Поддержка трамплина была предоставлена библиотекой Scala с объектом, scala.util.control.TailCalls
начиная с Scala 2.8.0 (выпущенной 14 июля 2010 г.). Функция может быть опционально аннотирована с помощью @tailrec
, в этом случае она не будет компилироваться, если она не является хвостовой рекурсией. [37]
Примером такой оптимизации может служить определение факториала . Например, рекурсивная версия факториала:
def factorial ( n : Int ): Int = если n == 0 , то 1 , иначе n * factorial ( n - 1 )
Можно оптимизировать версию с хвостовой рекурсией следующим образом:
@tailrec def факториал ( n : Int , accum : Int ): Int = если n == 0, то накапливается иначе факториал ( n - 1 , n * accum )
Однако это может поставить под угрозу совместимость с другими функциями из-за нового аргумента в ее определении, поэтому обычно используют замыкания, чтобы сохранить ее исходную сигнатуру:
def факториал ( n : Int ): Int = @tailrec цикл def ( текущий : Int , накопленный : Int ): Int = если n == 0, то аккумулировать цикл else ( текущий - 1 , n * накопление ) loop ( n , 1 ) // Вызов замыкания с использованием базового случая конечный факториал
Это обеспечивает оптимизацию хвостового вызова и, таким образом, предотвращает ошибку переполнения стека.
Scala имеет встроенную поддержку сопоставления с образцом , которую можно рассматривать как более сложную, расширяемую версию оператора switch , где произвольные типы данных могут быть сопоставлены (а не только простые типы, такие как целые числа, логические значения и строки), включая произвольную вложенность. Предоставляется специальный тип класса, известный как case class , который включает автоматическую поддержку сопоставления с образцом и может использоваться для моделирования алгебраических типов данных, используемых во многих функциональных языках программирования. (С точки зрения Scala, case class — это просто обычный класс, для которого компилятор автоматически добавляет определенные поведения, которые также могут быть предоставлены вручную, например, определения методов, обеспечивающих глубокие сравнения и хеширование, и деструктуризацию case class по параметрам его конструктора во время сопоставления с образцом.)
Пример определения алгоритма быстрой сортировки с использованием сопоставления с образцом:
def qsort ( list : List [ Int ]): List [ Int ] = list match case Nil => Nil case pivot :: tail => val ( lesser , rest ) = tail . partition ( _ < pivot ) qsort ( lesser ) ::: pivot :: qsort ( rest )
Идея здесь в том, что мы разбиваем список на элементы, меньшие опорного элемента, и элементы, не меньшие, рекурсивно сортируем каждую часть и вставляем результаты вместе с опорным элементом между ними. Это использует ту же стратегию «разделяй и властвуй», что и mergesort и другие алгоритмы быстрой сортировки.
Оператор match
используется для сопоставления с образцом объекта, хранящегося в list
. Каждое case
выражение проверяется по очереди, чтобы увидеть, будет ли оно соответствовать, и первое совпадение определяет результат. В этом случае Nil
соответствует только литеральному объекту Nil
, но pivot :: tail
соответствует непустому списку и одновременно деструктурирует список в соответствии с заданным образцом. В этом случае связанный код будет иметь доступ к локальной переменной с именем , pivot
содержащей заголовок списка, и другой переменной, tail
содержащей хвост списка. Обратите внимание, что эти переменные доступны только для чтения и семантически очень похожи на привязки переменных, установленные с помощью letоператора в Lisp и Scheme.
Сопоставление с образцом также происходит в объявлениях локальных переменных. В этом случае возвращаемое значение вызова tail.partition
— это кортеж — в данном случае два списка. (Кортежи отличаются от других типов контейнеров, например списков, тем, что они всегда имеют фиксированный размер, а элементы могут быть разных типов — хотя здесь они оба одинаковы.) Сопоставление с образцом — это самый простой способ извлечения двух частей кортежа.
Форма _ < pivot
представляет собой объявление анонимной функции с переменной-заполнителем; см. раздел выше об анонимных функциях.
Операторы списка ::
(добавляют элемент в начало списка, как cons
в Lisp и Scheme) и :::
(соединяют два списка вместе, как append
в Lisp и Scheme) оба появляются. Несмотря на внешность, в обоих этих операторах нет ничего «встроенного». Как указано выше, любая строка символов может служить именем функции, а метод, примененный к объекту, может быть записан в «инфиксном» стиле без точки или скобок. Строка выше написана так:
qsort(smaller) ::: pivot :: qsort(rest)
можно также записать так:
qsort(rest).::(pivot).:::(qsort(smaller))
в более стандартной нотации вызова метода. (Методы, заканчивающиеся двоеточием, являются правоассоциативными и привязываются к объекту справа.)
В приведенном выше примере сопоставления с образцом тело оператора match
представляет собой частичную функцию , которая состоит из ряда case
выражений, причем первое совпадающее выражение является преобладающим, аналогично телу оператора switch . Частичные функции также используются в части оператора, посвященной обработке исключений try
:
попробуйте ... поймать случай nfe : NumberFormatException => { println ( nfe ); Список ( 0 ) } случай _ => Ноль
Наконец, частичная функция может использоваться отдельно, и результат ее вызова эквивалентен выполнению match
над ней. Например, предыдущий код для быстрой сортировки можно записать так:
val qsort : List [ Int ] => List [ Int ] = case Nil => Nil case pivot :: tail => val ( lesser , rest ) = tail . partition ( _ < pivot ) qsort ( lesser ) ::: pivot :: qsort ( rest )
Здесь объявляется переменная только для чтения, тип которой — функция из списков целых чисел в списки целых чисел, и привязывается к частичной функции. (Обратите внимание, что единственный параметр частичной функции никогда явно не объявляется и не именуется.) Однако мы все равно можем вызвать эту переменную точно так же, как если бы она была обычной функцией:
scala > qsort ( Список ( 6 , 2 , 5 , 9 )) res32 : Список [ Int ] = Список ( 2 , 5 , 6 , 9 )
Scala — это чистый объектно-ориентированный язык в том смысле, что каждое значение является объектом . Типы данных и поведение объектов описываются классами и признаками . Абстракции классов расширяются путем создания подклассов и гибкого механизма композиции на основе миксинов, чтобы избежать проблем множественного наследования .
Черты — это замена интерфейсов Java в Scala . Интерфейсы в версиях Java ниже 8 сильно ограничены и могут содержать только абстрактные объявления функций. Это привело к критике, что предоставление удобных методов в интерфейсах неудобно (те же методы должны быть повторно реализованы в каждой реализации), а расширение опубликованного интерфейса обратно совместимым способом невозможно. Черты похожи на смешанные классы в том, что они обладают почти всей мощью обычного абстрактного класса, не имея только параметров класса (эквивалент Scala параметрам конструктора Java), поскольку черты всегда смешиваются с классом. super
Оператор ведет себя в чертах особым образом, позволяя объединять черты в цепочку с помощью композиции в дополнение к наследованию. Следующий пример представляет собой простую оконную систему:
абстрактный класс Window : // абстрактный def draw () class SimpleWindow extends Window : def draw () println ( "in SimpleWindow" ) // рисуем простое окно черта WindowDecoration расширяет Window trait HorizontalScrollbarDecoration extends WindowDecoration : // "abstract override" здесь необходим для работы "super()", потому что родительская // функция абстрактна. Если бы она была конкретной, обычного " override" было бы достаточно. abstract override def draw () println ( "in HorizontalScrollbarDecoration" ) super.draw () // теперь рисуем горизонтальную полосу прокрутки черта VerticalScrollbarDecoration расширяет WindowDecoration : abstract override def draw () println ( "in VerticalScrollbarDecoration" ) super.draw () // теперь рисуем вертикальную полосу прокрутки черта TitleDecoration расширяет WindowDecoration : abstract override def draw () println ( "in TitleDecoration" ) super.draw ( ) // теперь рисуем заголовок
Переменная может быть объявлена следующим образом:
val mywin = new SimpleWindow с VerticalScrollbarDecoration с HorizontalScrollbarDecoration с TitleDecoration
Результат вызова mywin.draw()
:
в TitleDecoration в HorizontalScrollbarDecoration в VerticalScrollbarDecoration в SimpleWindow
Другими словами, вызов draw
сначала выполнил код в TitleDecoration
(последний смешанный признак), затем (через super()
вызовы) прошёл обратно через другие смешанные признаки и в конечном итоге к коду в Window
, даже если ни один из признаков не унаследовал друг от друга . Это похоже на шаблон декоратора , но более лаконичен и менее подвержен ошибкам, поскольку не требует явной инкапсуляции родительского окна, явной пересылки функций, реализация которых не изменяется, или опоры на инициализацию связей сущностей во время выполнения. В других языках подобный эффект может быть достигнут во время компиляции с помощью длинной линейной цепочки наследования реализации , но с недостатком по сравнению со Scala в том, что для каждой возможной комбинации миксов должна быть объявлена одна линейная цепочка наследования.
Scala оснащена выразительной статической системой типов, которая в основном обеспечивает безопасное и согласованное использование абстракций. Однако система типов не является надежной . [38] В частности, система типов поддерживает:
Scala может выводить типы по использованию. Это делает большинство объявлений статических типов необязательными. Статические типы не обязательно должны быть явно объявлены, если только ошибка компилятора не указывает на необходимость. На практике некоторые объявления статических типов включены ради ясности кода.
Распространенная техника в Scala, известная как «enrich my library» [39] (первоначально названная « pimp my library » Мартином Одерски в 2006 году; [32] эта формулировка вызывала опасения из-за ее негативных коннотаций [40] и незрелости [41] ), позволяет использовать новые методы так, как если бы они были добавлены к существующим типам. Это похоже на концепцию методов расширения C# , но более мощно, поскольку техника не ограничивается добавлением методов и может, например, использоваться для реализации новых интерфейсов. В Scala эта техника включает объявление неявного преобразования из типа, «принимающего» метод, в новый тип (обычно класс), который оборачивает исходный тип и предоставляет дополнительный метод. Если метод не может быть найден для заданного типа, компилятор автоматически ищет любые применимые неявные преобразования в типы, которые предоставляют рассматриваемый метод.
Этот метод позволяет добавлять новые методы в существующий класс с помощью дополнительной библиотеки таким образом, что новую функциональность получает только код, импортирующий дополнительную библиотеку, а весь остальной код остается неизменным.
Следующий пример демонстрирует обогащение типа Int
методами isEven
и isOdd
:
объект MyExtensions : расширение ( i : Int ) def isEven = i % 2 == 0 def isOdd = ! я . isEven import MyExtensions . * // внести неявное обогащение в область действия 4 . isEven // -> true
Импорт членов MyExtensions
приводит к неявному преобразованию в класс расширения IntPredicates
в область действия. [42]
Стандартная библиотека Scala включает поддержку futures и promises , в дополнение к стандартным API Java concurrency. Первоначально она также включала поддержку модели акторов , которая теперь доступна как отдельная исходная платформа Akka [43] лицензированная Lightbend Inc. Акторы Akka могут быть распределены или объединены с программной транзакционной памятью ( транзакторами ). Альтернативные реализации коммуникационных последовательных процессов (CSP) для передачи сообщений на основе каналов — это Communicating Scala Objects, [44] или просто через JCSP .
Актер похож на экземпляр потока с почтовым ящиком. Его можно создать system.actorOf
, переопределив receive
метод для получения сообщений и используя !
метод (восклицательный знак) для отправки сообщения. [45]
В следующем примере показан EchoServer, который может получать сообщения, а затем печатать их.
val echoServer = actor ( new Act : become : case msg => println ( "echo " + msg ) ) echoServer ! "привет"
Scala также поставляется со встроенной поддержкой программирования параллельных данных в форме параллельных коллекций [46], интегрированных в его стандартную библиотеку, начиная с версии 2.9.0.
В следующем примере показано, как использовать параллельные коллекции для повышения производительности. [47]
val urls = Список ( "https://scala-lang.org" , "https://github.com/scala/scala" ) def fromURL ( url : String ) = scala.io.Source.fromURL ( url ) .getLines ( ) . mkString ( " \ n " ) val t = System . currentTimeMillis () urls . par . map ( fromURL ( _ )) // par возвращает параллельную реализацию коллекции println ( "time: " + ( System . currentTimeMillis - t ) + "ms" )
Помимо фьючерсов и обещаний, поддержки акторов и параллелизма данных , Scala также поддерживает асинхронное программирование с программной транзакционной памятью и потоками событий. [48]
Наиболее известным решением кластерных вычислений с открытым исходным кодом, написанным на Scala, является Apache Spark . Кроме того, Apache Kafka , очередь сообщений «публикация-подписка», популярная в Spark и других технологиях потоковой обработки, написана на Scala.
Существует несколько способов тестирования кода в Scala. ScalaTest поддерживает несколько стилей тестирования и может интегрироваться с фреймворками тестирования на основе Java. [49] ScalaCheck — это библиотека, похожая на QuickCheck в Haskell . [50] specs2 — это библиотека для написания спецификаций исполняемого программного обеспечения. [51] ScalaMock обеспечивает поддержку тестирования функций высокого порядка и каррированных функций. [52] JUnit и TestNG — популярные фреймворки тестирования, написанные на Java.
Scala часто сравнивают с Groovy и Clojure , двумя другими языками программирования, также использующими JVM. Существенные различия между этими языками существуют в системе типов, в степени, в которой каждый язык поддерживает объектно-ориентированное и функциональное программирование, и в сходстве их синтаксиса с синтаксисом Java.
Scala статически типизирован , в то время как Groovy и Clojure динамически типизированы . Это делает систему типов более сложной и трудной для понимания, но позволяет почти всем [38] ошибкам типов быть обнаруженными во время компиляции и может привести к значительно более быстрому выполнению. Напротив, динамическая типизация требует большего количества тестов для обеспечения корректности программы и, таким образом, как правило, медленнее, что обеспечивает большую гибкость и простоту программирования. Что касается различий в скорости, текущие версии Groovy и Clojure допускают необязательные аннотации типов, чтобы помочь программам избежать накладных расходов динамической типизации в случаях, когда типы практически статичны. Эти накладные расходы еще больше сокращаются при использовании последних версий JVM, которая была улучшена с помощью инструкции invoke dynamic для методов, которые определены с динамически типизированными аргументами. Эти достижения сокращают разрыв в скорости между статической и динамической типизацией, хотя статически типизированный язык, такой как Scala, по-прежнему является предпочтительным выбором, когда эффективность выполнения очень важна.
Что касается парадигм программирования, Scala наследует объектно-ориентированную модель Java и расширяет ее различными способами. Groovy, хотя также является строго объектно-ориентированным, больше сосредоточен на сокращении многословия. В Clojure объектно-ориентированное программирование не акцентируется, а функциональное программирование является главной силой языка. Scala также имеет множество возможностей функционального программирования, включая функции, найденные в продвинутых функциональных языках, таких как Haskell, и пытается быть агностиком между двумя парадигмами, позволяя разработчику выбирать между двумя парадигмами или, чаще, некоторой их комбинацией.
Что касается сходства синтаксиса с Java, Scala наследует большую часть синтаксиса Java, как и в случае с Groovy. Clojure, с другой стороны, следует синтаксису Lisp , который отличается как внешним видом, так и философией. [ необходима цитата ]
Еще в 2013 году, когда Scala была в версии 2.10, ThoughtWorks Technology Radar, который представляет собой основанный на мнении двухгодичный отчет группы старших технических специалистов, [139] рекомендовал внедрение Scala в своей категории языков и фреймворков. [140]
В июле 2014 года эта оценка была конкретизирована и теперь относится к «Scala, хорошим частям», что описывается как «Чтобы успешно использовать Scala, вам необходимо исследовать язык и иметь очень твердое мнение о том, какие части подходят именно вам, создавая свое собственное определение Scala, хороших частей». [141]
В выпуске опроса State of Java 2018 года [142] , в котором были собраны данные 5160 разработчиков по различным темам, связанным с Java, Scala занимает третье место по использованию альтернативных языков на JVM . По сравнению с выпуском опроса предыдущего года использование Scala среди альтернативных языков JVM упало с 28,4% до 21,5%, уступив место Kotlin , который вырос с 11,4% в 2017 году до 28,8% в 2018 году. Индекс популярности языков программирования [143] , который отслеживает поиски руководств по языкам, поставил Scala на 15-е место в апреле 2018 года с небольшой тенденцией к снижению и на 17-е место в январе 2021 года. Это делает Scala третьим по популярности языком на основе JVM после Java и Kotlin , занявших 12-е место.
Рейтинг языков программирования RedMonk, который составляет рейтинги на основе количества проектов GitHub и вопросов, заданных на Stack Overflow , в январе 2021 года поставил Scala на 14-е место. [ 144] Здесь Scala был помещен в группу языков второго уровня — впереди Go , PowerShell и Haskell и позади Swift , Objective-C , Typescript и R.
Индекс популярности языков программирования TIOBE [145] использует рейтинги поисковых систем в Интернете и аналогичный подсчет публикаций для определения популярности языка. В сентябре 2021 года он показал Scala на 31-м месте. В этом рейтинге Scala опередил Haskell (38-е место) и Erlang , но уступил Go (14-е место), Swift (15-е место) и Perl (19-е место).
По состоянию на 2022 год [обновлять]языки на основе JVM, такие как Clojure, Groovy и Scala, занимают высокие позиции, но все еще значительно менее популярны, чем исходный язык Java , который обычно занимает первые три места. [144] [145]
В ноябре 2011 года Yammer отошла от Scala по причинам, включавшим в себя кривую обучения для новых членов команды и несовместимость с одной версией компилятора Scala с другой. [175] В марте 2015 года бывший вице-президент группы Platform Engineering в Twitter Раффи Крикориан заявил, что он бы не выбрал Scala в 2011 году из-за ее кривой обучения . [176] В том же месяце старший вице-президент LinkedIn Кевин Скотт заявил о решении «минимизировать [их] зависимость от Scala». [177]
{{cite web}}
: Отсутствует или пусто |title=
( помощь )Создатели Scala на самом деле произносят его как scah-lah , как итальянское слово «лестница». Две «a» произносятся одинаково.
if
или while
не могут быть повторно реализованы. Существует исследовательский проект Scala-Virtualized, направленный на устранение этих ограничений: Adriaan Moors, Tiark Rompf, Philipp Haller и Martin Odersky. Scala-Virtualized. Труды семинара ACM SIGPLAN 2012 по частичной оценке и манипулированию программами , 117–120. Июль 2012 г.Также известен как шаблон pimp-my-library
implicit def IntPredicate(i: Int) = new IntPredicate(i)
. Класс также может быть определен как implicit class IntPredicates(val i: Int) extends AnyVal { ... }
, создавая так называемый класс значений , также введенный в Scala 2.10. Затем компилятор исключит фактические инстанциации и сгенерирует вместо них статические методы, что позволит методам расширения практически не иметь накладных расходов на производительность.бы я сделал по-другому четыре года назад, так это использовал Java и не использовал Scala в рамках этой переделки. [...] инженеру потребовалось бы два месяца, прежде чем он стал бы полностью продуктивным и начал писать код Scala.[ постоянная мертвая ссылка ]