Scala ( / ˈ s k ɑː l ə / SKAH -lah ) [8] — сильный статически типизированный высокоуровневый язык программирования общего назначения , поддерживающий как объектно-ориентированное программирование , так и функциональное программирование . Будучи краткими, [9] многие проектные решения Scala предназначены для устранения критики в адрес Java . [7]
Исходный код 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 представляет собой сочетание слов «масштабируемость » и «язык» , что означает, что оно предназначено для роста вместе с требованиями своих пользователей. [13]
Разработка Scala началась в 2001 году в Федеральной политехнической школе Лозанны (EPFL) (в Лозанне , Швейцария ) Мартином Одерски . Он стал продолжением работы над Funnel, языком программирования, сочетающим в себе идеи функционального программирования и сетей Петри . [14] Одерски ранее работал над Generic Java и javac , компилятором Java компании Sun. [14]
После внутреннего выпуска в конце 2003 года Scala был выпущен публично в начале 2004 года на платформе Java . [15] [7] [14] [16] Вторая версия (v2.0) последовала в марте 2006 года .
17 января 2011 года команда Scala выиграла пятилетний исследовательский грант в размере более 2,3 миллиона евро от Европейского исследовательского совета . [17] 12 мая 2011 года Одерский и его коллеги основали Typesafe Inc. (позже переименованную в Lightbend Inc. ), компанию, которая занимается предоставлением коммерческой поддержки, обучения и услуг для Scala. В 2011 году Typesafe получила инвестиции в размере 3 миллионов долларов от 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 году (версия 0.6). Версия v1.0.0-M1 была выпущена в июне 2018 года, а версия 1.1.1 — в сентябре 2020 года. [25]
Scala Native — это компилятор Scala , предназначенный для инфраструктуры компилятора LLVM для создания исполняемого кода, использующего облегченную управляемую среду выполнения, использующую сборщик мусора Boehm . Проект возглавляет Денис Шабалин, и его первый выпуск, 0.1, вышел 14 марта 2017 года. Разработка Scala Native началась в 2015 году с целью сделать компиляцию 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 (64-битная серверная виртуальная машина Java HotSpot(TM), Java 1.8.0_131). Введите выражения для оценки. Или попробуйте :help.scala> List(1, 2, 3).map(x => x * x) res0: List[Int] = List(1, 4, 9)скала>
В следующем примере показаны различия между синтаксисом Java и Scala. Функция mathFunction принимает целое число, возводит его в квадрат, а затем добавляет кубический корень этого числа к натуральному логарифму этого числа, возвращая результат (т. е. ):
Некоторые синтаксические различия в этом коде:
Int, Double, Boolean
вместо int, double, boolean
.def
.val
(указывает на неизменяемую переменную) или var
(указывает на изменяемую переменную).return
в функции не нужен (хотя и разрешен); значение последнего выполненного оператора или выражения обычно является значением функции.(Type) foo
Scala использует foo.asInstanceOf[Type]
или специализированную функцию, такую как toDouble
или toInt
.import foo.*;
Scala использует import foo._
.foo()
также можно вызывать просто foo
; метод thread.send(signo)
также можно вызвать просто thread send signo
; и метод foo.toString()
также можно назвать просто foo toString
.Эти синтаксические послабления предназначены для обеспечения поддержки языков, специфичных для предметной области .
Некоторые другие основные синтаксические различия:
array(i)
а не array[i]
. (Внутри Scala первый расширяется до array.apply(i), который возвращает ссылку)List[String]
а не как в Java List<String>
.void
в Scala есть настоящий одноэлементный класс Unit
(см. ниже).В следующем примере сравниваются определения классов в Java и Scala.
В приведенном выше коде показаны некоторые концептуальные различия между обработкой классов в Java и Scala:
object
вместо class
. Обычно статические переменные и методы размещаются в одноэлементном объекте с тем же именем, что и имя класса, который затем называется сопутствующим объектом . [15] (Базовый класс для объекта-одиночки имеет добавленный $
. Следовательно, для class Foo
объекта-компаньона object Foo
под капотом находится класс Foo$
, содержащий код объекта-компаньона, и один объект этого класса создается с использованием шаблона синглтона .)val
или var
поля также определяются с тем же именем и автоматически инициализируются из параметров класса. (Внутри внешний доступ к общедоступным полям всегда осуществляется через методы доступа (getter) и мутатора (setter), которые создаются автоматически. Функция доступа имеет то же имя, что и поле, поэтому в приведенном выше примере нет необходимости явно объявляйте методы доступа.) Обратите внимание, что альтернативные конструкторы также могут быть объявлены, как в 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
это ключевое слово, определенное в языке, но на самом деле это просто метод, принимающий аргумент thunk . Методы, использующие преобразователи или функции, часто помещают их во второй список параметров, что позволяет смешивать круглые и фигурные скобки. Синтаксис: Vector.fill(4) { math.random }
аналогичен Vector.fill(4)(math.random)
. Вариант с фигурными скобками позволяет выражению занимать несколько строк.map
, flatMap
и filter
.Сами по себе это может показаться сомнительным выбором, но в совокупности они служат цели, позволяющей определять предметно-ориентированные языки в Scala без необходимости расширения компилятора. Например, специальный синтаксис Erlang для отправки сообщения актору, т.е. actor ! message
может быть (и реализован) реализован в библиотеке Scala без необходимости расширения языка.
Java проводит четкое различие между примитивными типами (например, int
и boolean
) и ссылочными типами (любой класс ). Частью схемы наследования являются только ссылочные типы, происходящие от java.lang.Object
. В Scala все типы наследуются от класса верхнего уровня Any
, непосредственными дочерними элементами которого являются AnyVal
(типы значений, такие как Int
и Boolean
) и AnyRef
(ссылочные типы, как в Java). Это означает, что в Scala нет различия между примитивными типами и коробочными типами (например, int
против ). упаковка и распаковка полностью прозрачны для пользователя. Scala 2.10 позволяет пользователю определять новые типы значений.Integer
Вместо циклов Java foreach для обхода итератора в Scala есть for
-выражения, которые похожи на генераторы списков в таких языках, как Haskell, или комбинацию генераторов списков и выражений в Python . Выражения For с использованием yield
ключевого слова позволяют создать новую коллекцию путем итерации по существующей, возвращая новую коллекцию того же типа. Они преобразуются компилятором в последовательность map
, flatMap
и filter
вызываются. Там , где yield
он не используется, код приближается к императивному циклу путем перевода в foreach
.
Простой пример:
val s = for ( x <- от 1 до 25 , если x * x > 50 ) выход 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] , который позволяет добавлять новые методы к существующим типам.)
Более сложный пример перебора карты:
// Учитывая карту, на которой указаны пользователи Твиттера, упомянутые в наборе твитов, // и количество упоминаний каждого пользователя, найдите пользователей // на карте известных политиков и верните новую карту, показывающую только // Демократическую партию. политики (как объекты, а не строки). val dem_mentions = for ( упоминание , раз ) <- упоминает аккаунт < -accounts . получить ( упомянуть ) если аккаунт . партия == "Демократическая" доходность ( счет , раз )
Выражение (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» x )
Из-за вывода типа тип переменных, возвращаемые значения функции и многие другие выражения обычно могут быть опущены, поскольку компилятор может их определить. Примеры: val x = "foo"
(для неизменяемой константы или неизменяемого объекта ) или var x = 1.5
(для переменной, значение которой впоследствии можно изменить). Вывод типов в Scala по существу является локальным, в отличие от более глобального алгоритма Хиндли-Милнера , используемого в Haskell , ML и других более чисто функциональных языках. Это сделано для облегчения объектно-ориентированного программирования. В результате некоторые типы все еще необходимо объявлять (в первую очередь, параметры функций и типы возвращаемых рекурсивных функций ), например
def formatApples ( x : Int ) = "Я съел %d яблок" . формат ( х )
или (с типом возвращаемого значения, объявленным для рекурсивной функции)
def факториал ( x : Int ): Int = если x == 0 , то 1 иначе x * факториал ( x - 1 )
В Scala функции являются объектами, и для указания анонимных функций существует удобный синтаксис . Примером может служить выражение x => x < 2
, которое определяет функцию с одним параметром, которая сравнивает свой аргумент, чтобы убедиться, что он меньше 2. Это эквивалентно форме Lisp (lambda (x) (< x 2))
. Обратите внимание, что ни тип, x
ни возвращаемый тип не требуют явного указания и обычно могут быть выведены путем вывода типа ; но они могут быть указаны явно, например, как (x: Int) => x < 2
или даже (x: Int) => (x < 2): Boolean
.
Анонимные функции ведут себя как настоящие замыкания , поскольку они автоматически захватывают любые переменные, которые лексически доступны в среде включающей функции. Эти переменные будут доступны даже после завершения работы включающей функции, и, в отличие от анонимных внутренних классов Java , их не нужно объявлять как окончательные. (Такие переменные можно даже изменить, если они изменяемы, и измененное значение будет доступно при следующем вызове анонимной функции.)
Еще более короткая форма анонимной функции использует переменные -заполнители : например, следующее:
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
слова, а это означает, что код для создания значения переменной не будет оцениваться до тех пор, пока на переменную не будет сделана первая ссылка. Также существуют нестрогие коллекции различных типов (например, type Stream
, нестрогий связанный список), и любую коллекцию можно сделать нестрогой с помощью этого view
метода. Нестрогие коллекции обеспечивают хорошее семантическое соответствие таким вещам, как данные, создаваемые сервером, где оценка кода для создания последующих элементов списка (что, в свою очередь, запускает запрос на сервер, возможно, расположенный где-то еще в Интернете) только происходит, когда элементы действительно необходимы.
Языки функционального программирования обычно обеспечивают оптимизацию хвостовых вызовов , позволяющую широко использовать рекурсию без проблем с переполнением стека . Ограничения в байт-коде Java усложняют оптимизацию хвостовых вызовов в JVM. В общем, функция, вызывающая сама себя хвостовым вызовом, может быть оптимизирована, а взаимно рекурсивные функции — нет. В качестве обходного пути были предложены батуты . [36] Поддержка Trampoline обеспечивается библиотекой Scala вместе с объектом, scala.util.control.TailCalls
начиная с версии Scala 2.8.0 (выпущенной 14 июля 2010 г.). Функция может быть дополнительно помечена @tailrec
, и в этом случае она не будет компилироваться, если она не является хвостовой рекурсивной. [37]
Пример этой оптимизации может быть реализован с использованием определения факториала . Например, рекурсивная версия факториала:
def факториал ( n : Int ): Int = если n == 0 , то 1 иначе n * факториал ( 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 * накопление ) цикл ( n , 1 ) // Вызов замыкания с использованием базового случая конечный факториал
Это обеспечивает оптимизацию хвостового вызова и, таким образом, предотвращает ошибку переполнения стека.
В Scala имеется встроенная поддержка сопоставления с образцом , которую можно рассматривать как более сложную и расширяемую версию оператора переключения , в которой могут сопоставляться произвольные типы данных (а не просто простые типы, такие как целые числа, логические значения и строки), включая произвольные типы. гнездование. Предоставляется специальный тип класса, известный как класс Case , который включает автоматическую поддержку сопоставления с образцом и может использоваться для моделирования алгебраических типов данных , используемых во многих языках функционального программирования. (С точки зрения Scala, кейс-класс — это просто обычный класс, для которого компилятор автоматически добавляет определенные варианты поведения, которые также можно обеспечить вручную, например, определения методов, обеспечивающих глубокие сравнения и хеширование, а также деструктуризацию кейс-класса в его конструкторе. параметры во время сопоставления с образцом.)
Пример определения алгоритма быстрой сортировки с использованием сопоставления с образцом:
def qsort ( list : List [ Int ]): List [ Int ] = список совпадений регистр Nil => Nil case пивот :: хвост => val ( меньше , отдых ) = хвост . раздел ( _ < центр ) qsort ( меньше ) ::: центр :: qsort ( отдых )
Идея здесь состоит в том, что мы разделяем список на элементы меньше опорной точки и элементы не меньше, рекурсивно сортируем каждую часть и вставляем результаты вместе с опорной точкой между ними. При этом используется та же стратегия «разделяй и властвуй», что и в сортировке слиянием и других алгоритмах быстрой сортировки.
Оператор match
используется для сопоставления с образцом объекта, хранящегося в list
. Каждое case
выражение по очереди проверяется, будет ли оно совпадать, и первое совпадение определяет результат. В этом случае Nil
соответствует только литеральному объекту Nil
, но pivot :: tail
соответствует непустому списку и одновременно деструктурирует список в соответствии с заданным шаблоном. В этом случае связанный код будет иметь доступ к локальной переменной с именем, pivot
содержащей начало списка, и другой переменной, tail
содержащей конец списка. Обратите внимание, что эти переменные доступны только для чтения и семантически очень похожи на привязки переменных , устанавливаемые с помощью letоператора в Lisp и Scheme.
Сопоставление с образцом также происходит в объявлениях локальных переменных. В этом случае возвращаемое значение вызова — tail.partition
это кортеж — в данном случае два списка. (Кортежи отличаются от других типов контейнеров, например списков, тем, что они всегда имеют фиксированный размер, а элементы могут быть разных типов — хотя здесь они оба одинаковы.) Сопоставление с образцом — это самый простой способ получить две части кортеж.
Форма _ < pivot
представляет собой объявление анонимной функции с переменной-заполнителем; см. раздел выше об анонимных функциях.
Появляются операторы списка ::
(которые добавляют элемент в начало списка, как cons
в Lisp и Scheme) и :::
(который соединяет два списка вместе, как в Lisp и Scheme). append
Несмотря на внешний вид, ни в одном из этих операторов нет ничего «встроенного». Как указано выше, любая строка символов может служить именем функции, а метод, применяемый к объекту, может быть записан в «инфиксном» стиле без точки и круглых скобок. Строка выше, как написано:
qsort(smaller) ::: pivot :: qsort(rest)
также можно было бы записать так:
qsort(rest).::(pivot).:::(qsort(smaller))
в более стандартной записи вызова метода. (Методы, заканчивающиеся двоеточием, являются правоассоциативными и привязываются к объекту справа.)
В приведенном выше примере сопоставления с образцом тело оператора match
представляет собой частичную функцию , состоящую из серии case
выражений, среди которых преобладает первое совпадающее выражение, аналогично телу оператора переключения . Частичные функции также используются в части оператора, обрабатывающей исключения try
:
попробуйте ... поймать случай nfe : NumberFormatException => { println ( nfe ); Список ( 0 ) } case _ => Nil
Наконец, частичную функцию можно использовать отдельно, и результат ее вызова эквивалентен выполнению match
над ней операции. Например, предыдущий код быстрой сортировки можно записать так:
val qsort : List [ Int ] => List [ Int ] = case Nil => Nil case Pivot :: Tail => val ( меньше , остальное ) = Tail . раздел ( _ < центр ) qsort ( меньше ) ::: центр :: qsort ( отдых )
Здесь объявляется доступная только для чтения переменная , тип которой является функцией от списков целых чисел к спискам целых чисел, и привязывается к частичной функции. (Обратите внимание, что единственный параметр частичной функции никогда не объявляется и не именуется явно.) Однако мы все равно можем вызывать эту переменную точно так же, как если бы это была обычная функция:
scala > qsort ( Список ( 6 , 2 , 5 , 9 )) res32 : Список [ Int ] = Список ( 2 , 5 , 6 , 9 )
Scala — чистый объектно-ориентированный язык в том смысле, что каждое значение является объектом . Типы данных и поведение объектов описываются классами и признаками . Абстракции классов расширяются за счет создания подклассов и гибкого механизма композиции на основе миксинов , позволяющего избежать проблем множественного наследования .
Traits — это замена интерфейсов Java в Scala . Интерфейсы в версиях Java до 8 сильно ограничены и могут содержать только объявления абстрактных функций. Это привело к критике, что предоставление удобных методов в интерфейсах неудобно (одни и те же методы должны быть переопределены в каждой реализации), а расширение опубликованного интерфейса обратно совместимым способом невозможно. Трейты похожи на миксин- классы в том, что они обладают почти всеми возможностями обычного абстрактного класса, за исключением параметров класса (эквивалент параметров конструктора в Scala), поскольку трейты всегда смешиваются с классом. Оператор super
ведет себя особым образом в типах, позволяя объединять признаки с помощью композиции в дополнение к наследованию. Следующий пример представляет собой простую оконную систему:
абстрактный класс Window : // абстрактное определение ( ) класс SimpleWindow расширяет окно : def draw () println ( "in SimpleWindow" ) // рисуем базовое окно черта WindowDecoration расширяет окно типаж HorizontalScrollbarDecoration расширяет WindowDecoration : // здесь необходимо «абстрактное переопределение», чтобы «super()» работало, поскольку родительская // функция является абстрактной. Если бы это было конкретно, обычного «переопределения» было бы достаточно. абстрактное переопределение def draw () println ( «in HorizontalScrollbarDecoration» ) super . draw () // теперь рисуем горизонтальную полосу прокрутки черта ВертикальныйScrollbarDecoration расширяет WindowDecoration : абстрактное переопределение def draw () println ( «в ВертикальномScrollbarDecoration» ) super . draw () // теперь рисуем вертикальную полосу прокрутки черта TitleDecoration расширяет WindowDecoration : абстрактное переопределение def draw () println ( «in TitleDecoration» ) super . draw () // теперь рисуем строку заголовка
Переменную можно объявить следующим образом:
val mywin = новое SimpleWindow с вертикальным скроллбаром с горизонтальным скроллбаром с тайтломдекоратион
Результат вызова mywin.draw()
:
в TitleDecoration в горизонтальной полосе прокруткиDecoration в вертикальной полосе прокруткиDecoration в SimpleWindow
Другими словами, вызов draw
сначала выполнил код в TitleDecoration
(последний смешанный признак), затем (посредством super()
вызовов) перешел обратно через другие смешанные типажи и, в конечном итоге, к коду в Window
, даже если ни один из признаков не унаследован от друг друга . Это похоже на шаблон декоратора , но более лаконично и менее подвержено ошибкам, поскольку не требует явной инкапсуляции родительского окна, явной пересылки функций, реализация которых не изменяется, или использования инициализации отношений сущностей во время выполнения. . В других языках аналогичный эффект может быть достигнут во время компиляции с помощью длинной линейной цепочки наследования реализации , но с недостатком по сравнению со Scala, заключающимся в том, что для каждой возможной комбинации примесей придется объявлять одну линейную цепочку наследования.
Scala оснащена выразительной системой статических типов, которая в основном обеспечивает безопасное и последовательное использование абстракций. Однако система типов не является надежной . [38] В частности, система типов поддерживает:
Scala может выводить типы по использованию. Это делает большинство объявлений статических типов необязательными. Статические типы не обязательно объявлять явно, если только ошибка компилятора не укажет на необходимость. На практике для ясности кода включены некоторые объявления статических типов.
Распространенный метод в Scala, известный как «обогатить мою библиотеку» [39] (первоначально названный « прокачать мою библиотеку » Мартином Одерски в 2006 году; [32] по поводу этой формулировки были высказаны опасения из-за ее негативного подтекста [40] и незрелости [ 41] ), позволяет использовать новые методы так, как если бы они были добавлены к существующим типам. Это похоже на концепцию методов расширения C# , но более мощная, поскольку этот метод не ограничивается добавлением методов и может, например, использоваться для реализации новых интерфейсов. В Scala этот метод включает объявление неявного преобразования типа, «получающего» метод, в новый тип (обычно класс), который оборачивает исходный тип и предоставляет дополнительный метод. Если метод не может быть найден для данного типа, компилятор автоматически ищет любые применимые неявные преобразования к типам, которые предоставляют рассматриваемый метод.
Этот метод позволяет добавлять новые методы в существующий класс с использованием дополнительной библиотеки, так что только код, который импортирует дополнительную библиотеку, получает новые функциональные возможности, а весь остальной код не затрагивается.
В следующем примере показано обогащение типа Int
методами isEven
и isOdd
:
объект MyExtensions : расширение ( i : Int ) def isEven = i % 2 == 0 def isOdd = ! даже импортировать MyExtensions . _ // переносим неявное обогащение в область 4 . isEven // -> правда
Импорт членов MyExtensions
вводит неявное преобразование в класс расширения IntPredicates
в область видимости. [42]
Стандартная библиотека Scala включает поддержку фьючерсов и промисов в дополнение к стандартным API-интерфейсам параллелизма Java. Первоначально она также включала поддержку модели акторов , которая теперь доступна в виде отдельной доступной исходного кода платформы Akka [43], лицензированной Lightbend Inc. Акторы Akka могут быть распределены или объединены с программной транзакционной памятью ( транзакторами ). Альтернативными реализациями последовательных процессов связи (CSP) для передачи сообщений на основе каналов являются Communicating Scala Objects [44] или просто через JCSP .
Актер подобен экземпляру потока с почтовым ящиком. Его можно создать system.actorOf
, переопределив receive
метод получения сообщений и используя !
метод (восклицательный знак) для отправки сообщения. [45]
В следующем примере показан EchoServer, который может получать сообщения, а затем распечатывать их.
val echoServer = актер ( новый акт : стать : случай msg => println ( «echo» + msg ) ) эхосервер ! "привет"
Scala также имеет встроенную поддержку параллельного программирования данных в форме параллельных коллекций [46], интегрированных в стандартную библиотеку начиная с версии 2.9.0.
В следующем примере показано, как использовать параллельные коллекции для повышения производительности. [47]
val urls = List ( "https://scala-lang.org" , "https://github.com/scala/scala" ) def fromURL ( url : String ) = scala . ио . Источник . fromURL ( URL ) . получитьлинии (). mkString ( «\n» ) вал т = Система . URL-адреса currentTimeMillis () . пар . map ( fromURL ( _ )) // par возвращает параллельную реализацию коллекции println ( "time: " + ( System . currentTimeMillis - t ) + "ms" )
Помимо фьючерсов и обещаний, поддержки актеров и параллелизма данных , Scala также поддерживает асинхронное программирование с программной транзакционной памятью и потоками событий. [48]
Самым известным решением для кластерных вычислений с открытым исходным кодом, написанным на Scala, является Apache Spark . Кроме того, на Scala написана Apache Kafka , очередь сообщений публикации и подписки, популярная в Spark и других технологиях потоковой обработки.
Есть несколько способов тестирования кода в 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, которые были расширены за счет динамической инструкции вызова для методов, определенных с динамически типизированными аргументами. Эти достижения сокращают разрыв в скорости между статической и динамической типизацией, хотя статически типизированный язык, такой как Scala, по-прежнему является предпочтительным выбором, когда эффективность выполнения очень важна.
Что касается парадигм программирования, Scala наследует объектно-ориентированную модель Java и расширяет ее различными способами. Несмотря на то, что Groovy также сильно объектно-ориентирован, он больше ориентирован на снижение многословия. В Clojure объектно-ориентированному программированию уделяется меньше внимания, а функциональное программирование является основной сильной стороной языка. Scala также имеет множество средств функционального программирования, включая функции, имеющиеся в продвинутых функциональных языках, таких как Haskell , и пытается быть независимым между двумя парадигмами, позволяя разработчику выбирать между двумя парадигмами или, что чаще, некоторой их комбинацией.
Что касается сходства синтаксиса с Java, Scala наследует большую часть синтаксиса Java, как и в случае с Groovy. Clojure, с другой стороны, следует синтаксису Lisp , который отличается как по внешнему виду, так и по философии. [ нужна цитата ]
Еще в 2013 году, когда Scala была еще в версии 2.10, Технологический радар ThoughtWorks , который представляет собой основанный на мнениях двухгодичный отчет группы старших технологов, [122] рекомендовал внедрение Scala в категории языков и фреймворков. [123]
В июле 2014 года эта оценка стала более конкретной и теперь относится к «Scala, хорошие части», которая описывается так: «Чтобы успешно использовать Scala, вам необходимо изучить язык и иметь очень четкое мнение о том, какие части являются правильными». для вас, создающих собственное определение Scala, хорошие стороны». [124]
В опросе State of Java за 2018 год [125] , в котором были собраны данные от 5160 разработчиков по различным темам, связанным с Java, Scala занимает третье место с точки зрения использования альтернативных языков в JVM . По сравнению с опросом предыдущего года, использование Scala среди альтернативных языков JVM упало с 28,4% до 21,5%, обогнав Kotlin , доля которого выросла с 11,4% в 2017 году до 28,8% в 2018 году. Индекс популярности языка программирования [126] ] , который отслеживает поиск учебных пособий по языку, занял Scala 15-е место в апреле 2018 года с небольшой тенденцией к снижению и 17-е место в январе 2021 года. Это делает Scala третьим по популярности языком на основе JVM после Java и Kotlin , занявших 12-е место.
Рейтинг языков программирования RedMonk, который составляет рейтинг на основе количества проектов GitHub и вопросов, заданных на Stack Overflow , в январе 2021 года поставил Scala на 14-е место. [127] Здесь Scala был помещен в группу языков второго уровня — впереди Go , PowerShell и Haskell и после Swift , Objective-C , Typescript и R.
Индекс популярности языка программирования TIOBE [128] использует рейтинги поисковых систем в Интернете и аналогичный подсчет публикаций для определения популярности языка. В сентябре 2021 года он показал Scala на 31-м месте. В этом рейтинге Scala опередила Haskell (38-е место) и Erlang , но уступила Go (14-е место), Swift (15-е место) и Perl (19-е место).
По состоянию на 2022 год [обновлять]языки на основе JVM, такие как Clojure, Groovy и Scala, имеют высокие рейтинги, но все же значительно менее популярны, чем исходный язык Java , который обычно занимает три первых места. [127] [128]
В ноябре 2011 года Yammer отошла от Scala по причинам, включающим кривую обучения новых членов команды и несовместимость одной версии компилятора Scala с другой. [158] В марте 2015 года бывший вице-президент группы разработки платформ в Twitter Раффи Крикорян заявил, что он не выбрал бы Scala в 2011 году из-за ее кривой обучения . [159] В том же месяце старший вице-президент LinkedIn Кевин Скотт заявил о своем решении «минимизировать [свою] зависимость от Scala». [160]
if
или while
не могут быть повторно реализованы. Существует исследовательский проект Scala-Virtualized, направленный на устранение этих ограничений: Адриан Мурс, Тиарк Ромпф, Филипп Халлер и Мартин Одерски. Scala-виртуализация. Материалы семинара ACM SIGPLAN 2012 по частичной оценке и манипуляциям с программой , 117–120. Июль 2012.Также известен как шаблон «Прокачай мою библиотеку».
implicit def IntPredicate(i: Int) = new IntPredicate(i)
. Класс также может быть определен как implicit class IntPredicates(val i: Int) extends AnyVal { ... }
, создавая так называемый класс значений , также представленный в Scala 2.10. Затем компилятор исключит фактические экземпляры и вместо этого создаст статические методы, что позволит методам расширения практически не нагружать производительность.Что я бы сделал по-другому четыре года назад, так это использовал бы Java, а не Scala в рамках этой переписывания. [...] инженеру потребуется два месяца, прежде чем он сможет полностью продуктивно начать писать код Scala.[ постоянная мертвая ссылка ]