Scala (język programowania) - Scala (programming language)

Scala
Scala-full-color.svg
Paradygmat Wieloparadygmat : współbieżny , funkcjonalny , imperatywny , obiektowy
Zaprojektowany przez Marcin Oderski
Deweloper Laboratorium Metod Programowania École Polytechnique Fédérale de Lausanne
Po raz pierwszy pojawiły się 20 stycznia 2004 ; 17 lat temu ( 2004-01-20 )
Wersja stabilna
3.0.1  Edytuj to na Wikidanych / 9 lipca 2021 ; 2 miesiące temu ( 9 lipca 2021 )
Wersja zapoznawcza
3.0.0-RC3  Edytuj to na Wikidanych / 19 kwietnia 2021 ; 4 miesiące temu ( 19 kwietnia 2021 )
Dyscyplina pisania Wnioskowane , statyczne , mocne , strukturalne
Język implementacji Scala
Platforma Maszyna wirtualna Java (JVM)
JavaScript ( Scala.js )
LLVM ( Scala Native ) (eksperymentalna)
Licencja Apache 2.0
Rozszerzenia nazw plików .scala, .sc
Strona internetowa www .scala-lang .org
Wpływem
Common Lisp , Eiffel , Erlang , F# , Haskell , Java , OCaml , Oz , Pizza , Scheme , Smalltalk , Standard ML
Pod wpływem
Cejlon , Dłuto , Fantom , F# , Kotlin , Lasso , Czerwony

Scala ( / s k ɑː l ɑː / SKAH -lah ) jest silnym statycznie wpisane językiem programowania ogólnego przeznaczenia, który obsługuje zarówno programowanie obiektowe i programowanie funkcyjne . Zaprojektowane tak, aby były zwięzłe, wiele decyzji projektowych Scali jest skierowanych na krytykę Javy .

Kod źródłowy Scala można skompilować do kodu bajtowego Java i uruchomić na wirtualnej maszynie Java (JVM). Scala zapewnia interoperacyjność języków z Javą, dzięki czemu biblioteki napisane w dowolnym języku mogą być odwoływane bezpośrednio w kodzie Scala lub Java. Podobnie jak Java, Scala jest zorientowana obiektowo i używa składni zwanej nawiasem klamrowym, która jest podobna do języka C . Od Scali 3 istnieje również opcja użycia reguły off-side (wcięcia) do strukturyzacji bloków i jej użycie jest zalecane. Martin Odersky powiedział, że okazała się to najbardziej produktywna zmiana wprowadzona w Scali 3.

W przeciwieństwie do Javy, Scala ma wiele funkcji funkcjonalnych języków programowania , takich jak Scheme , Standard ML i Haskell , w tym currying , immutability , leniwa ocena i dopasowanie wzorców . Posiada również zaawansowany system typów obsługujący algebraiczne typy danych , kowariancję i kontrawariancję , typy wyższego rzędu (ale nie typy o wyższej randze ) oraz typy anonimowe . Inne cechy Scali nieobecne w Javie to przeciążanie operatorów , parametry opcjonalne, parametry nazwane i nieprzetworzone ciągi znaków . Odwrotnie, cechą Javy nie w Scali są sprawdzane wyjątki , co okazało się kontrowersyjne.

Nazwa Scala jest połączeniem skalowalności i języka , co oznacza, że ​​została zaprojektowana tak, aby rozwijać się wraz z wymaganiami użytkowników.

Historia

Konstrukcja Scala rozpoczął w 2001 w Politechnika Federalna w Lozannie (EPFL) (w Lozannie , Szwajcaria ) przez Martin Odersky . Był kontynuacją prac nad Funnel, językiem programowania łączącym idee z programowania funkcjonalnego i sieci Petriego . Odersky wcześniej pracował nad Generic Java i javac , kompilatorem Java firmy Sun.

Po wewnętrznym wydaniu pod koniec 2003 r., Scala została wydana publicznie na początku 2004 r. na platformie Java . Druga wersja (v2.0) pojawiła się w marcu 2006 r.

17 stycznia 2011 r. zespół Scali otrzymał od Europejskiej Rady ds . Badań Naukowych pięcioletni grant badawczy w wysokości ponad 2,3 mln EUR . W dniu 12 maja 2011 r. Odersky wraz ze współpracownikami uruchomił Typesafe Inc. (później przemianowaną na Lightbend Inc. ), firmę mającą zapewnić wsparcie komercyjne, szkolenia i usługi dla Scala. Typesafe otrzymał w 2011 roku 3 miliony dolarów inwestycji od Greylock Partners .

Platformy i licencja

Scala działa na platformie Java ( wirtualna maszyna Java ) i jest kompatybilna z istniejącymi programami Java . Ponieważ aplikacje na Androida są zazwyczaj pisane w Javie i tłumaczone z kodu bajtowego Javy na kod bajtowy Dalvik (który może być dalej tłumaczony na natywny kod maszynowy podczas instalacji) po spakowaniu, kompatybilność Scala z Javą sprawia, że ​​dobrze nadaje się do programowania na Androida, tym bardziej w przypadku podejścia funkcjonalnego jest preferowany.

Referencyjna dystrybucja oprogramowania Scala, w tym kompilator i biblioteki, jest wydana na licencji Apache .

Inne kompilatory i cele

Scala.js to kompilator Scala, który kompiluje się do JavaScript, umożliwiając pisanie programów Scala, które mogą działać w przeglądarkach internetowych lub Node.js . Kompilator, rozwijany od 2013 roku, został ogłoszony jako nie eksperymentalny w 2015 roku (wersja 0.6). Wersja v1.0.0-M1 została wydana w czerwcu 2018 r., a wersja 1.1.1 we wrześniu 2020 r.

Scala Native to kompilator Scala, który jest przeznaczony dla infrastruktury kompilatora LLVM w celu tworzenia kodu wykonywalnego, który używa uproszczonego zarządzanego środowiska wykonawczego, które korzysta z modułu odśmiecania pamięci Boehm . Projektem kieruje Denys Shabalin, a jego pierwsze wydanie, 0.1, miało miejsce 14 marca 2017 r. Rozwój Scala Native rozpoczął się w 2015 r., mając na celu szybsze kompilowanie JVM niż kompilacja just-in-time poprzez wyeliminowanie początkowej kompilacji środowiska uruchomieniowego kodu, a także zapewniając możliwość bezpośredniego wywoływania natywnych procedur.

Referencyjny kompilator Scala przeznaczony dla platformy .NET Framework i jej środowiska uruchomieniowego języka wspólnego został wydany w czerwcu 2004 r., ale został oficjalnie usunięty w 2012 r.

Przykłady

Przykład „Witaj świecie”

Program Hello World napisany w Scali ma następującą postać:

 object HelloWorld extends App {
   println("Hello, World!")
 }

W przeciwieństwie do samodzielnej aplikacji Hello World dla Java , nie ma deklaracji klasy i nic nie jest deklarowane jako statyczne; obiekt Singleton stworzony z przedmiotów kluczowych jest używany zamiast.

Gdy program znajduje się w pliku HelloWorld.scala , użytkownik kompiluje go poleceniem:

$ scalac HelloWorld.scala

i prowadzi to z

$ scala HelloWorld

Jest to analogiczne do procesu kompilowania i uruchamiania kodu Java. Rzeczywiście, model kompilacji i wykonywania Scali jest identyczny z modelem Javy, dzięki czemu jest kompatybilny z narzędziami do budowania Javy, takimi jak Apache Ant .

Krótsza wersja programu Scala „Hello World” to:

println("Hello, World!")

Scala zawiera interaktywną powłokę i obsługę skryptów. Zapisany w pliku o nazwie HelloWorld2.scala, można go uruchomić jako skrypt za pomocą polecenia:

$ scala HelloWorld2.scala

Polecenia można również wprowadzać bezpośrednio do interpretera Scala, korzystając z opcji -e :

$ scala -e 'println("Hello, World!")'

Wyrażenia można wprowadzać interaktywnie w REPL :

$ scala
Welcome to Scala 2.12.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_131).
Type in expressions for evaluation. Or try :help.

scala> List(1, 2, 3).map(x => x * x)
res0: List[Int] = List(1, 4, 9)

scala>

Podstawowy przykład

Poniższy przykład pokazuje różnice między składnią Java i Scala. Funkcja mathFunction pobiera liczbę całkowitą, podnosi ją do kwadratu, a następnie dodaje pierwiastek sześcienny tej liczby do logarytmu naturalnego tej liczby, zwracając wynik (tj. ):

// Java:
int mathFunction(int num) {
    int numSquare = num*num;
    return (int) (Math.cbrt(numSquare) +
      Math.log(numSquare));
}
// Scala: Direct conversion from Java

// no import needed; scala.math
// already imported as `math`
def mathFunction(num: Int): Int = {
  var numSquare: Int = num*num
  return (math.cbrt(numSquare) + math.log(numSquare)).
    asInstanceOf[Int]
}
// Scala: More idiomatic
// Uses type inference, omits `return` statement,
// uses `toInt` method, declares numSquare immutable

import math._
def mathFunction(num: Int) = {
  val numSquare = num*num
  (cbrt(numSquare) + log(numSquare)).toInt
}

Niektóre różnice składniowe w tym kodzie to:

  • Scala nie wymaga średników na końcu instrukcji.
  • Typy wartości są pisane wielkimi literami: Int, Double, Booleanzamiast int, double, boolean.
  • Typy parametrów i zwracane są następujące, jak w Pascal , a nie poprzedzają, jak w C .
  • Metody muszą być poprzedzone znakiem def.
  • Zmienne lokalne lub klasie muszą być poprzedzone val(wskazuje na niezmienne zmiennej) lub var(wskazuje na zmienny zmiennej).
  • returnOperator jest konieczne w funkcji (chociaż dopuszczalne); wartość ostatniej wykonanej instrukcji lub wyrażenia jest zwykle wartością funkcji.
  • Zamiast operatora rzutowania Java (Type) fooScala używa foo.asInstanceOf[Type]specjalnej funkcji, takiej jak toDoublelub toInt.
  • Zamiast Javy import foo.*;Scala używa import foo._.
  • Funkcję lub metodę foo()można również wywołać jako just foo; metoda thread.send(signo)może być również wywołana jako just thread send signo; i method foo.toString()może być również wywołana jako just foo toString.

Te złagodzenia składniowe mają na celu umożliwienie obsługi języków specyficznych dla domeny .

Kilka innych podstawowych różnic składniowych:

  • Odwołania do tablic są pisane jak wywołania funkcji, np. array(i)zamiast array[i]. (Wewnętrznie w Scali ta pierwsza rozwija się do array.apply(i), która zwraca odwołanie)
  • Typy generyczne są pisane jako np , List[String]a nie Javy List<String>.
  • Zamiast pseudo-typu voidScala ma rzeczywistą klasę singletona Unit (patrz niżej).

Przykład z klasami

Poniższy przykład zestawia definicję klas w Javie i Scali.

// Java:
public class Point {
  private final double x, y;

  public Point(final double x, final double y) {
    this.x = x;
    this.y = y;
  }

  public Point(
    final double x, final double y,
    final boolean addToGrid
  ) {
    this(x, y);

    if (addToGrid)
      grid.addToGrid(this);
  }

  public Point() {
    this(0.0, 0.0);
  }

  public double getX() {
    return x;
  }

  public double getY() {
    return y;
  }

  double distanceToPoint(final Point other) {
    return distanceBetweenPoints(x, y,
      other.x, other.y);
  }

  private static Grid grid = new Point();

  static double distanceBetweenPoints(
      final double x1, final double y1,
      final double x2, final double y2
  ) {
    return Math.hypot(x1 - x2, y1 - y2);
  }
}
// Scala
class Point(
    val x: Double, val y: Double,
    addToGrid: Boolean = false
) {
  import Point._

  if (addToGrid)
    grid.addToGrid(this)

  def this() = this(0.0, 0.0)

  def distanceToPoint(other: Point) =
    distanceBetweenPoints(x, y, other.x, other.y)
}

object Point {
  private val grid = new Point()

  def distanceBetweenPoints(x1: Double, y1: Double,
      x2: Double, y2: Double) = {
    math.hypot(x1 - x2, y1 - y2)
  }
}

Powyższy kod pokazuje niektóre koncepcyjne różnice między obsługą klas w Javie i Scali:

  • Scala nie ma statycznych zmiennych ani metod. Zamiast tego ma pojedyncze obiekty , które zasadniczo są klasami z tylko jedną instancją. Obiekty Singleton są deklarowane przy użyciu objectzamiast class. Często umieszcza się statyczne zmienne i metody w pojedynczym obiekcie o tej samej nazwie co nazwa klasy, który jest wtedy znany jako obiekt towarzyszący . (Podstawowa klasa dla obiektu singleton ma $dodany class Foododatek . Dlatego w przypadku obiektu towarzyszącego object Foopod maską znajduje się klasa Foo$zawierająca kod obiektu towarzyszącego i jeden obiekt tej klasy jest tworzony przy użyciu wzorca singleton .)
  • W miejsce parametrów konstruktora Scala ma parametry klasy , które są umieszczane na klasie, podobnie jak parametry funkcji. Po zadeklarowaniu z modyfikatorem vallub varpola są również definiowane o tej samej nazwie i automatycznie inicjowane z parametrów klasy. (Pod maską zewnętrzny dostęp do pól publicznych zawsze odbywa się za pomocą metod akcesora (gettera) i mutatora (ustawiacza), które są tworzone automatycznie. Funkcja akcesora ma taką samą nazwę jak pole, dlatego w powyższym przykładzie nie jest konieczne jawnie zadeklaruj metody akcesora.) Zauważ, że można również zadeklarować alternatywne konstruktory, tak jak w Javie. Kod, który trafiłby do domyślnego konstruktora (inny niż inicjowanie zmiennych składowych) trafia bezpośrednio na poziom klasy.
  • Domyślna widoczność w Scali to public.

Funkcje (w odniesieniu do Javy)

Scala ma ten sam model kompilacji co Java i C# , a mianowicie oddzielne kompilowanie i dynamiczne ładowanie klas , dzięki czemu kod Scala może wywoływać biblioteki Java.

Charakterystyki operacyjne Scali są takie same jak w Javie. Kompilator Scala generuje kod bajtowy, który jest prawie identyczny z kodem generowanym przez kompilator Java. W rzeczywistości kod Scala może zostać zdekompilowany do czytelnego kodu Java, z wyjątkiem pewnych operacji konstruktorów. Dla wirtualnej maszyny Java (JVM) kod Scala i kod Java są nie do odróżnienia. Jedyną różnicą jest jedna dodatkowa biblioteka uruchomieniowa, scala-library.jar.

Scala dodaje dużą liczbę funkcji w porównaniu z Javą i ma pewne fundamentalne różnice w podstawowym modelu wyrażeń i typów, które sprawiają, że język jest teoretycznie czystszy i eliminuje kilka przypadków narożnych w Javie. Z perspektywy Scali jest to praktycznie ważne, ponieważ kilka dodatkowych funkcji w Scali jest również dostępnych w C#.

Elastyczność syntaktyczna

Jak wspomniano powyżej, Scala ma dużą elastyczność składniową w porównaniu z Javą. Oto kilka przykładów:

  • Średniki są niepotrzebne; linie są automatycznie łączone, jeśli zaczynają się lub kończą tokenem, który normalnie nie może znajdować się w tej pozycji, lub jeśli występują niezamknięte nawiasy lub nawiasy.
  • Jako operator infiksowy można użyć dowolnej metody, np. "%d apples".format(num)i "%d apples" format numsą równoważne. W rzeczywistości operatory arytmetyczne lubią +i <<są traktowane tak samo jak inne metody, ponieważ nazwy funkcji mogą składać się z sekwencji dowolnych symboli (z kilkoma wyjątkami dotyczącymi takich rzeczy jak nawiasy, nawiasy i nawiasy klamrowe, które muszą być obsługiwane specjalnie); jedyne specjalne traktowanie, jakiemu poddawane są takie nazwane symbolami metody, dotyczy postępowania z pierwszeństwem.
  • Metody applyi updatekrótkie formy składniowe. foo()—gdzie foojest wartością (pojedynczy obiekt lub instancja klasy) — jest skrótem od foo.apply()i foo() = 42jest skrótem od foo.update(42). Podobnie foo(42)jest skrótem od foo.apply(42)i foo(4) = 2jest skrótem od foo.update(4, 2). Jest to używane w klasach kolekcji i rozciąga się na wiele innych przypadków, takich jak komórki STM .
  • Scala rozróżnia metody no-parens ( def foo = 42) i empty-parens ( def foo() = 42). Podczas wywoływania metody empty-parens nawiasy można pominąć, co jest przydatne podczas wywoływania bibliotek Java, które nie znają tego rozróżnienia, np. using foo.toStringzamiast foo.toString(). Zgodnie z konwencją, metoda powinna być zdefiniowana za pomocą empty-parens, gdy wykonuje efekty uboczne .
  • Nazwy metod kończące się dwukropkiem ( :) oczekują argumentu po lewej stronie, a odbiornika po prawej stronie. Na przykład 4 :: 2 :: Niljest to samo co Nil.::(2).::(4), pierwsza forma odpowiada wizualnie wynikowi (lista z pierwszym elementem 4 i drugim elementem 2).
  • Zmienne treści klasy mogą być zaimplementowane w sposób przezroczysty jako oddzielne metody pobierające i ustawiające. Dla trait FooLike { var bar: Int }realizacji może być . Witryna połączeń nadal będzie mogła używać zwięzłego .object Foo extends FooLike { private var x = 0; def bar = x; def bar_=(value: Int) { x = value }} } }foo.bar = 42
  • W wywołaniach metod dozwolone jest używanie nawiasów klamrowych zamiast nawiasów. Pozwala to na implementacje czysto biblioteczne nowych struktur kontrolnych. Na przykład breakable { ... if (...) break() ... }wygląda tak, jakby breakablebyło słowem kluczowym zdefiniowanym w języku, ale w rzeczywistości jest tylko metodą przyjmującą argument thunk . Metody korzystające z elementów lub funkcji często umieszczają je na drugiej liście parametrów, co pozwala mieszać składnię nawiasów i nawiasów klamrowych: Vector.fill(4) { math.random }jest taka sama jak Vector.fill(4)(math.random). Wariant z nawiasami klamrowymi pozwala, aby wyrażenie obejmowało wiele linii.
  • For-expressions (wyjaśnione poniżej) może pomieścić dowolny typ, który definiuje metody monadyczne, takie jak map, flatMapi filter.

Same w sobie mogą wydawać się wątpliwymi wyborami, ale łącznie służą do umożliwienia definiowania języków specyficznych dla domeny w Scali bez konieczności rozszerzania kompilatora. Na przykład specjalna składnia Erlanga do wysyłania wiadomości do aktora, tzn. actor ! messagemoże być (i jest) zaimplementowana w bibliotece Scala bez konieczności stosowania rozszerzeń językowych.

Zunifikowany system typów

Java dokonuje ostrego rozróżnienia między typami pierwotnymi (np. inti boolean) a typami referencyjnymi (dowolną klasą ). Tylko typy referencyjne są częścią schematu dziedziczenia, wywodzącego się z java.lang.Object. W Scali wszystkie typy dziedziczą z klasy najwyższego poziomu Any, której bezpośrednimi dziećmi są AnyVal(typy wartościowe, takie jak Inti Boolean) oraz AnyRef(typy referencyjne, jak w Javie). Oznacza to, że w Scali nie ma rozróżnienia między typami pierwotnymi a typami pudełkowymi (np. intvs. Integer). Boks i unboxing jest całkowicie przezroczysty dla użytkownika. Scala 2.10 pozwala na definiowanie przez użytkownika nowych typów wartości.

For-wyrażenia

Zamiast pętli Java „ foreach ” służących do przechodzenia przez iterator, Scala ma for-wyrażenia, które są podobne do wyrażeń list w językach takich jak Haskell lub kombinacji wyrażeń list i wyrażeń generatora w Pythonie . Wyrażenia for używające yieldsłowa kluczowego umożliwiają wygenerowanie nowej kolekcji przez iterację istniejącej, zwracając nową kolekcję tego samego typu. Są one tłumaczone przez kompilator w szeregu map, flatMapi filterpołączeń. Tam, gdzie yieldnie jest używany, kod przybliża się do pętli w stylu imperatywnym, tłumacząc na foreach.

Prosty przykład to:

val s = for (x <- 1 to 25 if x*x > 50) yield 2*x

Wynikiem jego uruchomienia jest następujący wektor:

Vector(16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50)

(Zauważ, że wyrażenie 1 to 25nie jest specjalną składnią. Metoda tojest raczej zdefiniowana w standardowej bibliotece Scala jako metoda rozszerzająca na liczbach całkowitych, przy użyciu techniki znanej jako niejawne konwersje, która umożliwia dodawanie nowych metod do istniejących typów.)

Bardziej złożonym przykładem iteracji po mapie jest:

// Given a map specifying Twitter users mentioned in a set of tweets,
// and number of times each user was mentioned, look up the users
// in a map of known politicians, and return a new map giving only the
// Democratic politicians (as objects, rather than strings).
val dem_mentions = for {
    (mention, times) <- mentions
    account          <- accounts.get(mention)
    if account.party == "Democratic"
  } yield (account, times)

Wyrażenie (mention, times) <- mentionsjest przykładem dopasowania wzorca (patrz poniżej). Iteracja po mapie zwraca zestaw krotek klucz-wartość , a dopasowywanie wzorców umożliwia łatwą destrukturyzację krotek na oddzielne zmienne dla klucza i wartości. Podobnie wynik zrozumienia zwraca również krotki klucz-wartość, które są automatycznie budowane w postaci mapy, ponieważ obiekt źródłowy (ze zmiennej mentions) jest mapą. Zwróć uwagę, że jeśli mentionszamiast tego przechowuje listę, zestaw, tablicę lub inną kolekcję krotek, dokładnie ten sam kod powyżej da nową kolekcję tego samego typu.

Tendencje funkcjonalne

Obsługując wszystkie funkcje obiektowe dostępne w Javie (iw rzeczywistości rozszerzając je na różne sposoby), Scala zapewnia również dużą liczbę możliwości, które normalnie można znaleźć tylko w funkcjonalnych językach programowania . Razem te cechy pozwalają na pisanie programów Scala w prawie całkowicie funkcjonalnym stylu, a także umożliwiają mieszanie stylów funkcjonalnych i obiektowych.

Przykładami są:

Wszystko jest wyrażeniem

W przeciwieństwie do C czy Java, ale podobnie do języków takich jak Lisp , Scala nie rozróżnia instrukcji i wyrażeń . Wszystkie stwierdzenia są w rzeczywistości wyrażeniami, które mają określoną wartość. Funkcje, które zostałyby zadeklarowane jako zwracające voidw C lub Javie, a takie instrukcje whilelogicznie nie zwracają wartości, są w Scali uważane za zwracające typ Unit, który jest typem singletona , z tylko jednym obiektem tego typu. Funkcje i operatory, które w ogóle nigdy nie zwracają (np. throwoperator lub funkcja, która zawsze kończy działanie nielokalnie przy użyciu wyjątku) logicznie mają zwracany typ Nothing, specjalny typ nie zawierający żadnych obiektów; czyli typ dolny , czyli podklasa każdego możliwego typu. (To z kolei sprawia, że ​​typ jest Nothingkompatybilny z każdym typem, umożliwiając poprawne działanie wnioskowania o typie ).

Podobnie if-then-else„instrukcja” jest w rzeczywistości wyrażeniem, które daje wartość, czyli wynik oceny jednej z dwóch gałęzi. Oznacza to, że taki blok kodu można wstawić wszędzie tam, gdzie pożądane jest wyrażenie, eliminując potrzebę operatora trójargumentowego w Scali:

// Java:
int hexDigit = x >= 10 ? x + 'A' - 10 : x + '0';
// Scala:
val hexDigit = if (x >= 10) x + 'A' - 10 else x + '0'

Z podobnych powodów returnstwierdzenia są w Scali niepotrzebne, aw rzeczywistości są odradzane. Podobnie jak w Lispie, ostatnie wyrażenie w bloku kodu jest wartością tego bloku kodu, a jeśli blok kodu jest treścią funkcji, zostanie zwrócony przez funkcję.

Aby było jasne, że wszystkie funkcje są wyrażeniami, nawet metody, które zwracają, Unitsą pisane ze znakiem równości

def printValue(x: String): Unit = {
  println("I ate a %s".format(x))
}

lub równoważnie (z wnioskowaniem o typie i pominięciem niepotrzebnych nawiasów klamrowych):

def printValue(x: String) = println("I ate a %s" format x)

Wnioskowanie o typie

Ze względu na wnioskowanie o typie typ zmiennych, wartości zwracane przez funkcję i wiele innych wyrażeń można zazwyczaj pominąć, ponieważ kompilator może to wywnioskować. Przykładami są val x = "foo"(dla niezmiennej stałej lub niezmiennego obiektu ) lub var x = 1.5(dla zmiennej, której wartość można później zmienić). Wnioskowanie o typie w Scali jest zasadniczo lokalne, w przeciwieństwie do bardziej globalnego algorytmu Hindleya-Milnera używanego w Haskell , ML i innych bardziej czysto funkcjonalnych językach. Ma to na celu ułatwienie programowania obiektowego. W rezultacie pewne typy nadal muszą być zadeklarowane (w szczególności parametry funkcji i typy zwracane funkcji rekurencyjnych ), np.

def formatApples(x: Int) = "I ate %d apples".format(x)

lub (z typem zwracanym zadeklarowanym dla funkcji rekurencyjnej)

def factorial(x: Int): Int =
  if (x == 0)
    1
  else
    x*factorial(x - 1)

Funkcje anonimowe

W Scali funkcje są obiektami i istnieje wygodna składnia do określania funkcji anonimowych . Przykładem jest wyrażenie x => x < 2, które określa funkcję z jednym parametrem, porównując jej argument, aby sprawdzić, czy jest mniejszy niż 2. Jest to odpowiednik formy Lisp (lambda (x) (< x 2)). Należy zauważyć, że ani typ, xani zwracany typ nie muszą być jawnie określone i można je ogólnie wywnioskować na podstawie wnioskowania o typie ; ale można je wyraźnie określić, np. jako (x: Int) => x < 2lub nawet (x: Int) => (x < 2): Boolean.

Funkcje anonimowe zachowują się jak prawdziwe domknięcia , ponieważ automatycznie przechwytują wszystkie zmienne, które są leksykalnie dostępne w środowisku funkcji otaczającej. Zmienne te będą dostępne nawet po zwróceniu funkcji otaczającej i w przeciwieństwie do anonimowych klas wewnętrznych Javy nie muszą być deklarowane jako finalne. (Możliwe jest nawet modyfikowanie takich zmiennych, jeśli są one modyfikowalne, a zmodyfikowana wartość będzie dostępna przy następnym wywołaniu funkcji anonimowej.)

Jeszcze krótsza forma funkcji anonimowej wykorzystuje zmienne zastępcze : Na przykład:

list map { x => sqrt(x) }

można napisać bardziej zwięźle jako

list map { sqrt(_) }

lub nawet

list map sqrt

Niezmienność

Scala wymusza rozróżnienie między zmiennymi niezmiennymi i zmiennymi. Zmienne mutowalne są deklarowane za pomocą varsłowa kluczowego, a wartości niezmienne są deklarowane za pomocą valsłowa kluczowego. Zmienna zadeklarowana przy użyciu valsłowa kluczowego nie może zostać ponownie przypisana w taki sam sposób, jak zmienna zadeklarowana przy użyciu finalsłowa kluczowego nie może zostać ponownie przypisana w Javie. val's są tylko płytko niezmienne, to znaczy, że obiekt, do którego odwołuje się val, nie ma gwarancji, że jest niezmienny.

Klasy niezmienne są jednak zalecane przez konwencję, a standardowa biblioteka Scala zapewnia bogaty zestaw niezmiennych klas kolekcji . Scala zapewnia zmienne i niezmienne warianty większości klas kolekcji, a niezmienna wersja jest zawsze używana, chyba że zmienna wersja jest jawnie importowana. Niezmienne warianty to trwałe struktury danych, które zawsze zwracają zaktualizowaną kopię starego obiektu zamiast aktualizować go w sposób destrukcyjny na miejscu. Przykładem tego są niezmienne listy połączone, w których dodawanie elementu do listy odbywa się poprzez zwrócenie nowego węzła listy składającego się z elementu i odniesienia do końca listy. Dodanie elementu do listy można wykonać tylko przez dołączenie wszystkich elementów ze starej listy do nowej listy zawierającej tylko nowy element. W ten sam sposób wstawienie elementu w środku listy spowoduje skopiowanie pierwszej połowy listy, ale zachowa odniesienie do drugiej połowy listy. Nazywa się to udostępnianiem strukturalnym. Pozwala to na bardzo łatwą współbieżność — nie są potrzebne żadne blokady, ponieważ żadne obiekty współdzielone nie są nigdy modyfikowane.

Ocena leniwa (nieścisła)

Ocena jest domyślnie surowa („chętna”). Innymi słowy, Scala ocenia wyrażenia, gdy tylko są dostępne, a nie w razie potrzeby. Istnieje jednak możliwość zadeklarowania zmiennej nieścisłej („leniwej”) za pomocą lazysłowa kluczowego, co oznacza, że ​​kod generujący wartość zmiennej nie będzie oceniany przed pierwszym odwołaniem do zmiennej. Istnieją również nieścisłe kolekcje różnych typów (takie jak type Stream, nieścisła lista połączona), a każda kolekcja może być nieścisła za pomocą viewmetody. Kolekcje nieścisłe zapewniają dobre dopasowanie semantyczne do takich rzeczy, jak dane produkowane przez serwer, gdzie ocena kodu w celu wygenerowania późniejszych elementów listy (co z kolei wyzwala żądanie do serwera, prawdopodobnie znajdującego się w innym miejscu w sieci) dzieje się, gdy elementy są rzeczywiście potrzebne.

Rekurencja ogona

Funkcjonalne języki programowania zwykle zapewniają optymalizację wywołań końcowych, aby umożliwić szerokie wykorzystanie rekurencji bez problemów z przepełnieniem stosu . Ograniczenia w kodzie bajtowym Javy komplikują optymalizację wywoływania ogona w JVM. Ogólnie rzecz biorąc, funkcja, która wywołuje samą siebie z wywołaniem końcowym, może być zoptymalizowana, ale funkcje wzajemnie rekurencyjne nie. Jako obejście zaproponowano trampoliny . Obsługa trampolin została udostępniona przez bibliotekę Scala wraz z obiektem scala.util.control.TailCallsod wersji Scala 2.8.0 (wydanej 14 lipca 2010). Funkcja może być opcjonalnie opatrzona adnotacją @tailrec, w takim przypadku nie będzie się kompilować, chyba że jest rekurencyjna.

Klasy przypadków i dopasowywanie wzorców

Scala ma wbudowaną obsługę dopasowywania wzorców , którą można traktować jako bardziej wyrafinowaną, rozszerzalną wersję instrukcji switch , w której można dopasowywać dowolne typy danych (a nie tylko proste typy, takie jak liczby całkowite, wartości logiczne i łańcuchy), w tym arbitralne zagnieżdżanie. Dostępny jest specjalny typ klasy znany jako klasa przypadku , który obejmuje automatyczną obsługę dopasowywania wzorców i może być używany do modelowania algebraicznych typów danych używanych w wielu funkcjonalnych językach programowania. (Z perspektywy Scali klasa case to po prostu normalna klasa, do której kompilator automatycznie dodaje pewne zachowania, które mogą być również dostarczane ręcznie, np. definicje metod zapewniające głębokie porównania i haszowanie oraz destrukturyzację klasy case w jej konstruktorze parametry podczas dopasowywania wzorca.)

Przykładową definicją algorytmu szybkiego sortowania wykorzystującego dopasowywanie wzorców jest:

def qsort(list: List[Int]): List[Int] = list match {
  case Nil => Nil
  case pivot :: tail =>
    val (smaller, rest) = tail.partition(_ < pivot)
    qsort(smaller) ::: pivot :: qsort(rest)
}

Pomysł polega na tym, że dzielimy listę na elementy mniejsze niż oś i elementy nie mniej, rekursywnie sortujemy każdą część i wklejamy wyniki razem z osią obrotową pomiędzy nimi. Wykorzystuje to tę samą strategię dziel i zwyciężaj , co w przypadku sortowania przez scalanie i innych algorytmów szybkiego sortowania.

matchOperatora służy do dopasowywania wzorców od przedmiotu przechowywanego w list. Każde casewyrażenie jest po kolei próbowane, aby sprawdzić, czy będzie pasować, a pierwsze dopasowanie określa wynik. W tym przypadku Nildopasowuje tylko dosłowny obiekt Nil, ale pivot :: taildopasowuje niepustą listę i jednocześnie destrukturyzuje listę zgodnie z podanym wzorcem. W takim przypadku powiązany kod będzie miał dostęp do zmiennej lokalnej o nazwie pivotzawierającej początek listy i innej zmiennej tailzawierającej koniec listy. Zauważ, że te zmienne są tylko do odczytu i są semantycznie bardzo podobne do powiązań zmiennych ustanowionych za pomocą letoperatora w Lisp i Scheme.

Dopasowywanie wzorców odbywa się również w deklaracjach zmiennych lokalnych. W tym przypadku wartością zwracaną przez wywołanie to tail.partitionjest krotka — w tym przypadku dwie listy. (Krótki różnią się od innych typów kontenerów, np. list, tym, że zawsze mają stały rozmiar, a elementy mogą być różnych typów — chociaż tutaj oba są takie same.) Dopasowywanie wzorców jest najłatwiejszym sposobem pobrania dwóch części krotka.

Formularz _ < pivotjest deklaracją funkcji anonimowej ze zmienną zastępczą; zobacz sekcję powyżej dotyczącą funkcji anonimowych.

Operatory list ::(który dodaje element na początku listy, podobnie jak consw Lisp i Scheme) i :::(który dodaje dwie listy razem, podobnie jak appendw Lisp i Scheme) pojawiają się oba. Wbrew pozorom żaden z tych operatorów nie ma nic „wbudowanego”. Jak wspomniano powyżej, dowolny ciąg symboli może służyć jako nazwa funkcji, a metoda zastosowana do obiektu może być napisana w stylu " infix " bez kropki i nawiasów. Powyższy wiersz jak napisano:

qsort(smaller) ::: pivot :: qsort(rest)

można by też napisać tak:

qsort(rest).::(pivot).:::(qsort(smaller))

w bardziej standardowej notacji wywołania metody. (Metody kończące się dwukropkiem są prawostronnie skojarzone i wiążą się z obiektem po prawej stronie).

Funkcje częściowe

W powyższym przykładzie dopasowywania wzorców treść matchoperatora jest funkcją częściową , która składa się z serii casewyrażeń, w których przeważa pierwsze pasujące wyrażenie, podobne do treści instrukcji switch . Funkcje częściowe są również używane w części tryinstrukcji dotyczącej obsługi wyjątków :

try {
  ...
} catch {
  case nfe:NumberFormatException => { println(nfe); List(0) }
  case _ => Nil
}

Wreszcie, funkcja częściowa może być używana samodzielnie, a wynik jej wywołania jest równoznaczny z wykonaniem matchnad nią. Na przykład poprzedni kod dla quicksort można napisać w ten sposób:

val qsort: List[Int] => List[Int] = {
  case Nil => Nil
  case pivot :: tail =>
    val (smaller, rest) = tail.partition(_ < pivot)
    qsort(smaller) ::: pivot :: qsort(rest)
}

Tutaj zadeklarowana jest zmienna tylko do odczytu, której typem jest funkcja z list liczb całkowitych do list liczb całkowitych i wiąże ją z funkcją częściową. (Zauważ, że pojedynczy parametr funkcji częściowej nigdy nie jest jawnie zadeklarowany ani nazwany.) Jednak nadal możemy wywołać tę zmienną dokładnie tak, jakby była normalną funkcją:

scala> qsort(List(6,2,5,9))
res32: List[Int] = List(2, 5, 6, 9)

Rozszerzenia obiektowe

Scala jest językiem czysto obiektowym w tym sensie, że każda wartość jest obiektem . Typy danych i zachowania obiektów są opisane przez klasy i cechy . Abstrakcje klas są rozszerzane przez tworzenie podklas i elastyczny mechanizm kompozycji oparty na domieszkach , aby uniknąć problemów z wielokrotnym dziedziczeniem .

Cechy są zamiennikiem Scali dla interfejsów Javy . Interfejsy w wersjach Java poniżej 8 są bardzo ograniczone, mogą zawierać jedynie abstrakcyjne deklaracje funkcji. Doprowadziło to do krytyki, że dostarczanie wygodnych metod w interfejsach jest niewygodne (te same metody muszą być ponownie zaimplementowane w każdej implementacji), a rozszerzenie opublikowanego interfejsu w sposób zgodny z poprzednimi wersjami jest niemożliwe. Cechy są podobne do klas mieszanych pod tym względem, że mają prawie całą moc zwykłej klasy abstrakcyjnej, pozbawione tylko parametrów klasy (odpowiednik parametrów konstruktora w Scali), ponieważ cechy są zawsze mieszane z klasą. superOperatora zachowuje się specjalnie w cechy, pozwalające cechy, być połączony za pomocą kompozycji oprócz spadku. Poniższy przykład to prosty system okienny:

abstract class Window {
  // abstract
  def draw()
}

class SimpleWindow extends Window {
  def draw() {
    println("in SimpleWindow")
    // draw a basic window
  }
}

trait WindowDecoration extends Window { }

trait HorizontalScrollbarDecoration extends WindowDecoration {
  // "abstract override" is needed here for "super()" to work because the parent
  // function is abstract. If it were concrete, regular "override" would be enough.
  abstract override def draw() {
    println("in HorizontalScrollbarDecoration")
    super.draw()
    // now draw a horizontal scrollbar
  }
}

trait VerticalScrollbarDecoration extends WindowDecoration {
  abstract override def draw() {
    println("in VerticalScrollbarDecoration")
    super.draw()
    // now draw a vertical scrollbar
  }
}

trait TitleDecoration extends WindowDecoration {
  abstract override def draw() {
    println("in TitleDecoration")
    super.draw()
    // now draw the title bar
  }
}

Zmienną można zadeklarować w następujący sposób:

val mywin = new SimpleWindow with VerticalScrollbarDecoration with HorizontalScrollbarDecoration with TitleDecoration

Wynik wywołania mywin.draw()to:

in TitleDecoration
in HorizontalScrollbarDecoration
in VerticalScrollbarDecoration
in SimpleWindow

Innymi słowy, wywołanie drawpierwszego wykonania kodu in TitleDecoration(ostatnia zmieszana cecha), a następnie (poprzez super()wywołania) przerzucenie z powrotem przez inne wmieszane cechy i ostatecznie do kodu w Window, mimo że żadna z cech odziedziczonych po siebie nawzajem . Jest to podobne do wzorca dekoratora , ale jest bardziej zwięzłe i mniej podatne na błędy, ponieważ nie wymaga jawnego hermetyzacji okna nadrzędnego, jawnego przekazywania funkcji, których implementacja nie została zmieniona, ani polegania na inicjalizacji relacji jednostek w czasie wykonywania . W innych językach podobny efekt można osiągnąć w czasie kompilacji z długim liniowym łańcuchem dziedziczenia implementacji , ale z tą wadą w porównaniu do Scali, że jeden liniowy łańcuch dziedziczenia musiałby być zadeklarowany dla każdej możliwej kombinacji domieszek.

Ekspresyjny system pisma

Scala wyposażona jest w wyrazisty system typów statycznych, który przede wszystkim wymusza bezpieczne i spójne korzystanie z abstrakcji. System czcionek nie jest jednak dźwięczny . W szczególności system typu obsługuje:

Scala potrafi wywnioskować typy według użycia. Dzięki temu większość deklaracji typu statycznego jest opcjonalna. Typy statyczne nie muszą być jawnie deklarowane, chyba że błąd kompilatora wskazuje na potrzebę. W praktyce niektóre deklaracje typu statycznego są zawarte w celu zapewnienia przejrzystości kodu.

Rodzaj wzbogacenia

Powszechna technika w Scali, znana jako „wzbogacanie mojej biblioteki” (pierwotnie nazwana „ odpicuj moją bibliotekę ” przez Martina Odersky'ego w 2006 roku; pojawiły się obawy dotyczące tego sformułowania ze względu na jego negatywne konotacje i niedojrzałość), pozwala na stosowanie nowych metod tak, jakby zostały dodane do istniejących typów. Jest to podobne do koncepcji metod rozszerzających w języku C# , ale bardziej zaawansowane, ponieważ technika ta nie ogranicza się do dodawania metod i może na przykład służyć do implementacji nowych interfejsów. W Scali ta technika polega na deklarowaniu niejawnej konwersji typu „otrzymującego” metodę na nowy typ (zazwyczaj klasę), który otacza oryginalny typ i zapewnia dodatkową metodę. Jeśli nie można znaleźć metody dla danego typu, kompilator automatycznie wyszukuje wszelkie odpowiednie niejawne konwersje na typy, które zapewniają daną metodę.

Ta technika umożliwia dodawanie nowych metod do istniejącej klasy przy użyciu biblioteki dodatków, dzięki czemu tylko kod, który importuje bibliotekę dodatków, otrzymuje nową funkcjonalność i nie ma to wpływu na cały inny kod.

Poniższy przykład pokazuje wzbogacenie type Into metody isEveni isOdd:

object MyExtensions {
  implicit class IntPredicates(i: Int) {
    def isEven = i % 2 == 0
    def isOdd  = !isEven
  }
}

import MyExtensions._  // bring implicit enrichment into scope
4.isEven  // -> true

Importowanie elementów członkowskich MyExtensionspowoduje przeniesienie niejawnej konwersji do klasy rozszerzenia IntPredicatesdo zakresu.

Konkurencja

Standardowa biblioteka Scali zawiera wsparcie dla futures i obietnic , oprócz standardowych interfejsów Java współbieżności API. Pierwotnie zawierał również wsparcie dla modelu aktora , który jest teraz dostępny jako oddzielna platforma open source Akka stworzona przez Lightbend Inc. Aktorzy Akka mogą być dystrybuowani lub połączeni z programową pamięcią transakcyjną ( transactorami ). Alternatywnymi implementacjami komunikujących się procesów sekwencyjnych (CSP) dla przekazywania komunikatów w oparciu o kanał są Communicating Scala Objects lub po prostu przez JCSP .

Aktor jest jak instancja wątku ze skrzynką pocztową. Można go utworzyć system.actorOf, zastępując receivemetodę odbierania wiadomości i używając metody !(wykrzyknik) do wysyłania wiadomości. Poniższy przykład pokazuje EchoServer, który może odbierać wiadomości, a następnie je drukować.

val echoServer = actor(new Act {
  become {
    case msg => println("echo " + msg)
  }
})
echoServer ! "hi"

Scala ma również wbudowaną obsługę programowania równoległego danych w postaci kolekcji równoległych zintegrowanych ze standardową biblioteką od wersji 2.9.0.

Poniższy przykład pokazuje, jak używać kolekcji równoległych w celu poprawy wydajności.

val urls = List("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 returns parallel implementation of a collection
println("time: " + (System.currentTimeMillis - t) + "ms")

Poza przyszłościami i obietnicami, obsługą aktorów i równoległością danych , Scala obsługuje również programowanie asynchroniczne z programową pamięcią transakcyjną i strumieniami zdarzeń.

Obliczenia klastrowe

Najbardziej znanym rozwiązaniem klastrowym typu open source napisanym w Scali jest Apache Spark . Ponadto Apache Kafka , kolejka komunikatów publikowania i subskrybowania popularna w Spark i innych technologiach przetwarzania strumieni, jest napisana w Scali.

Testowanie

Istnieje kilka sposobów testowania kodu w Scali. ScalaTest obsługuje wiele stylów testowania i może integrować się z frameworkami testowymi opartymi na Javie. ScalaCheck to biblioteka podobna do QuickCheck Haskella . specs2 to biblioteka do pisania specyfikacji wykonywalnego oprogramowania. ScalaMock zapewnia wsparcie dla testowania funkcji wysokiego rzędu i curried. JUnit i TestNG to popularne frameworki testowe napisane w Javie.

Wersje

Wersja Wydany Cechy
1.0.0-b2 8 grudnia 2003 r. _
1.1.0-b1 19 lutego 2004
  • scala.Wyliczanie
  • Licencja Scala została zmieniona na poprawioną licencję BSD
1.1.1 23 marca 2004 r.
  • Wsparcie dla statycznych klas wewnętrznych Java
  • Ulepszenia klas bibliotecznych dla Iterable, Array, xml.Elem, Buffer
1.2.0 9 czerwca 2004
  • Wyświetlenia
  • Literały XML (do „porzucenia w niedalekiej przyszłości, do zastąpienia interpolacją ciągów XML”)
1.3.0 16 września 2004
  • Wsparcie dla Microsoft .NET
  • Zamknięcia metod
  • Zmieniono składnię typów metod bezparametrowych z [] Tna=> T
1.4.0 20 czerwca 2005
  • Atrybuty
  • matchsłowo kluczowe zastępuje matchmetodę
  • Eksperymentalne wsparcie dla typów runtime
2,0 12 marca 2006
  • Kompilator całkowicie przepisany w Scala
  • Eksperymentalne wsparcie dla generyków Java
  • impliciti requiressłowa kluczowe
  • match słowo kluczowe dozwolone tylko wrostkiem
  • withłącznik jest dozwolony tylko po extendsklauzuli
  • Znaki nowej linii mogą być używane jako separatory instrukcji zamiast średników
  • Wzorce dopasowania wyrażenia regularnego ograniczone tylko do wzorców sekwencji
  • For-comprehensive dopuszczają wartości i definicje wzorców
  • Parametry klasy mogą być poprzedzone przez val lub var
  • Widoczność prywatna ma kwalifikatory
2.1.0 17 marca 2006
  • narzędzie sbaz zintegrowane z dystrybucją Scala
  • matchsłowo kluczowe zastępuje matchmetodę
  • Eksperymentalne wsparcie dla typów runtime
2.1.8 23 sierpnia 2006
  • Chroniona widoczność ma kwalifikatory
  • Do prywatnych członków klasy można się odwoływać z modułu towarzyszącego klasy i na odwrót
  • Uogólnione wyszukiwanie niejawne
  • Wpisane dopasowanie wzoru zaostrzone dla typów singleton
2.3.0 23 listopada 2006
  • Funkcje zwracające Unitnie muszą jawnie określać typu zwracanego
  • Zmienne typu i typy są rozróżniane podczas dopasowywania wzorców
  • Alli AllRefprzemianowany na NothingiNull
2.4.0 9 marca 2007 r.
  • privatea protectedmodyfikatory akceptują [this]kwalifikator
  • Krotki można pisać w nawiasach okrągłych
  • Główny konstruktor klasy może być teraz oznaczony jako prywatny lub chroniony
  • Atrybuty zmienione na adnotacje z nową składnią
  • Własne aliasy
  • Operatory można łączyć z przypisaniem
2.5.0 2 maja 2007
  • Parametry typu i składowe typu abstrakcyjnego mogą również abstrahować od konstruktorów typu
  • Pola obiektu można zainicjować przed wywołaniem konstruktorów nadrzędnych
  • Zmiana składni dla zrozumień
  • Niejawne funkcje anonimowe (z podkreśleniami dla parametrów)
  • Dopasowywanie wzorców funkcji anonimowych rozszerzone do obsługi dowolnej sztuki
2.6.0 27 lipca 2007
  • Typy egzystencjalne
  • Leniwe wartości
  • Typy strukturalne
2.7.0 7 lutego 2008
  • Ogólne typy Java obsługiwane domyślnie
  • Rozszerzona funkcjonalność klas przypadków
2.8.0 14 lipca 2010
  • Zmień wspólną, ujednoliconą i kompleksową strukturę dla typów kolekcji.
  • Specjalizacja typu
  • Argumenty nazwane i domyślne
  • Pakiet obiektów
  • Ulepszone adnotacje
2.9.0 12 maja 2011
  • Kolekcje równoległe
  • AppCecha bezpieczeństwa wątku zastępuje Applicationcechę
  • DelayedInit cecha
  • Ulepszenia współdziałania Java
2.10 4 stycznia 2013
  • Klasy wartości
  • Klasy niejawne
  • Interpolacja ciągów
  • Przyszłości i obietnice
  • Dynamiczne i stosowane Dynamiczne
  • Rodzaje metod zależnych:
    • def identity(x: AnyRef): x.type = x // the return type says we return exactly what we got
  • Nowy emiter kodu bajtowego oparty na ASM:
    • Może celować w JDK 1.5, 1.6 i 1.7
    • Domyślnie emituje 1,6 bajtowego kodu
    • Stary backend 1.5 jest przestarzały
  • Nowy dopasowywanie wzorców: przepisany od zera, aby wygenerować bardziej niezawodny kod (bez wykładniczego powiększania)
    • generowanie i analizy kodu są teraz niezależne (te ostatnie można wyłączyć za pomocą -Xno-patmat-analysis)
  • Ulepszenia Scaladoc
    • Implicity (flaga -implicity)
    • Diagramy (flaga -diagrams, wymaga graphviz)
    • Grupy (-grupy)
  • Modułowe funkcje językowe
  • Kolekcje równoległe można teraz konfigurować za pomocą niestandardowych pul wątków
  • Aktorzy Akka są teraz częścią dystrybucji
    • scala.actors zostały przestarzałe, a implementacja akka jest teraz dołączona do dystrybucji.
  • Ulepszenia w wydajności
    • Szybsza wkładka
    • Zakres#suma wynosi teraz O(1)
  • Aktualizacja biblioteki ForkJoin
  • Poprawki w niezmiennym TreeSet/TreeMap
  • Ulepszenia funkcji częściowych
  • Dodanie ??? i NotImplementedError
  • Dodanie klas typu IsTraversableOnce + IsTraversableLike dla metod rozszerzających
  • Wycofanie i czyszczenie
  • Wycofanie składni zmiennoprzecinkowej i ósemkowej literalnej
  • Usunięto scala.dbc

Funkcje eksperymentalne

  • Odbicie Scali
  • Makra
2.10.2 6 czerwca 2013 _
2.10.3 1 października 2013 _
2.10.4 18 marca 2014 _
2.10.5 5 marca 2015 _
2.11.0 21 kwietnia 2014
  • Ulepszenia wydajności kolekcji
  • Ulepszenia wydajności kompilatora
2.11.1 20 maja 2014 _
2.11.2 22 lipca 2014 _
2.11.4 31 października 2014 _
2.11.5 8 stycznia 2015 _
2.11.6 5 marca 2015 _
2.11.7 23 czerwca 2015 _
2.11.8 8 marca 2016 _
2.11.11 18 kwietnia 2017 _
2.11.12 13 listopada 2017 _
2.12.0 3 listopada 2016
2.12.1 5 grudnia 2016 _
2.12.2 18 kwietnia 2017 _
2.12.3 26 lipca 2017 _
2.12.4 17 października 2017 _
2.12.5 15 marca 2018 _
2.12.6 27 kwietnia 2018 _
2.12.7 27 września 2018 _
2.12.8 4 grudnia 2018
  • Pierwsze wydanie Scala 2.12 z licencją zmienioną na Apache v2.0
2.12.9 5 sierpnia 2019 _
2.12.10 10 września 2019 _
2.12.11 16 marca 2020 _
2.12.12 13 lipca 2020 _
2.12.13 12 stycznia 2021 _
2.13.0 11 czerwca 2019
  • Przeprojektowana biblioteka zbiorów standardowych
  • Typy dosłowne
  • Częściowa unifikacja typu
  • Domniemane według nazwy
  • Optymalizacje kompilatora
2.13.1 18 września 2019 _
2.13.2 22 kwietnia 2020 _
2.13.3 25 czerwca 2020 _
2.13.4 19 listopada 2020 _
2.13.5 22 lutego 2021 _
3.0.0 13 maja 2021 _

Porównanie z innymi językami JVM

Scala jest często porównywana z Groovy i Clojure , dwoma innymi językami programowania również używającymi JVM. Zasadnicze różnice między tymi językami istnieją w systemie typów, w zakresie, w jakim każdy język obsługuje programowanie obiektowe i funkcjonalne, oraz w podobieństwie ich składni do składni Javy.

Scala jest typowana statycznie , podczas gdy Groovy i Clojure są typowane dynamicznie . To sprawia, że ​​system typów jest bardziej złożony i trudniejszy do zrozumienia, ale pozwala na wychwycenie prawie wszystkich błędów typów w czasie kompilacji i może skutkować znacznie szybszym wykonaniem. W przeciwieństwie do tego dynamiczne typowanie wymaga więcej testów, aby zapewnić poprawność programu, a zatem jest generalnie wolniejsze, aby zapewnić większą elastyczność i prostotę programowania. Jeśli chodzi o różnice w szybkości, obecne wersje Groovy i Clojure umożliwiają opcjonalne adnotacje typu, aby pomóc programom uniknąć narzutu dynamicznego pisania w przypadkach, gdy typy są praktycznie statyczne. Ten narzut jest dodatkowo redukowany w przypadku korzystania z najnowszych wersji maszyny JVM, która została wzbogacona o dynamiczną instrukcję wywołania dla metod, które są zdefiniowane za pomocą argumentów wpisanych dynamicznie. Te postępy zmniejszają różnicę w szybkości między typowaniem statycznym i dynamicznym, chociaż język statycznie typowany, taki jak Scala, jest nadal preferowanym wyborem, gdy wydajność wykonania jest bardzo ważna.

Jeśli chodzi o paradygmaty programowania, Scala dziedziczy obiektowy model Javy i rozszerza go na różne sposoby. Groovy, choć również silnie zorientowany obiektowo, jest bardziej skoncentrowany na redukowaniu gadatliwości. W Clojure programowanie obiektowe jest pomijane, a programowanie funkcjonalne jest główną siłą języka. Scala ma również wiele funkcji programowania funkcjonalnego, w tym funkcje znalezione w zaawansowanych językach funkcjonalnych, takich jak Haskell , i stara się być agnostyczna między dwoma paradygmatami, pozwalając programiście wybrać między dwoma paradygmatami lub, częściej, pewną ich kombinacją.

Jeśli chodzi o podobieństwo składni z Javą, Scala dziedziczy większość składni Javy, tak jak w przypadku Groovy. Z drugiej strony Clojure podąża za składnią Lisp , która różni się zarówno wyglądem, jak i filozofią. Jednak nauka Scali jest również uważana za trudną ze względu na wiele zaawansowanych funkcji. Tak nie jest w przypadku Groovy, mimo że jest on również językiem bogatym w funkcje, głównie dlatego, że został zaprojektowany głównie jako język skryptowy.

Przyjęcie

Rankingi językowe

Od 2021 r. języki oparte na JVM, takie jak Clojure, Groovy, Kotlin, Scala, są znacznie mniej popularne niż oryginalny język Java, który zwykle znajduje się w pierwszych trzech miejscach i który jednocześnie ewoluuje w czasie.

Indeks popularności języka programowania, który śledzi wyszukiwania samouczków językowych, uplasował Scala na 15. miejscu w kwietniu 2018 r. z niewielką tendencją spadkową i 17. w styczniu 2021 r. To sprawia, że ​​Scala jest trzecim najpopularniejszym językiem opartym na JVM po Javie i Kotlinie , zajmując 12. miejsce .

Indeks TIOBE programowania języka popularność wykorzystuje Internet rankingach wyszukiwania i podobnych publikacji liczenia w celu określenia języka popularność. Od września 2021 r. pokazuje Scala na 31. miejscu. W tym rankingu Scala wyprzedza Haskella (38.) i Erlanga , ale poniżej Go (14.), Swifta (15.) i Perla (19.).

Rankingi języków programowania RedMonk, które ustalają rankingi na podstawie liczby projektów GitHub i pytań zadawanych na Stack Overflow , plasują Scala na 14. miejscu. Tutaj Scala znajduje się w drugiej grupie języków — przed Go , PowerShell i Haskell , a za Swift , Objective-C , Typescript i R .

W edycji 2018 ankiety State of Java , w której zebrano dane od 5160 programistów na różne tematy związane z Javą, Scala zajmuje trzecie miejsce pod względem wykorzystania alternatywnych języków na JVM. W porównaniu z ubiegłoroczną edycją badania, użycie Scali wśród alternatywnych języków JVM spadło z 28,4% do 21,5%, wyprzedzając Kotlina, który wzrósł z 11,4% w 2017 r. do 28,8% w 2018 r.

W 2013 roku, kiedy Scala była w wersji 2.10, ThinkWorks Technology Radar, który jest opartym na opinii, półrocznym raportem grupy starszych technologów, zalecił przyjęcie Scali w kategorii języków i frameworków. W lipcu 2014 r. ocena ta została doprecyzowana i teraz odnosi się do „Scala, dobre części”, która jest opisana jako „Aby skutecznie używać Scali, musisz zbadać język i mieć bardzo silną opinię na temat tego, które części są właściwe dla Ciebie, tworząc własną definicję Scali, dobrych części”.

Firmy

  • W kwietniu 2009 r. Twitter ogłosił, że zmienił dużą część swojego zaplecza z Ruby na Scala i zamierza przekonwertować resztę.
  • Gilt używa Scali i Play Framework .
  • Foursquare używa Scali i Lift .
  • Coursera używa Scala i Play Framework .
  • Apple Inc. używa Scali w niektórych zespołach, wraz z Javą i frameworkiem Play.
  • Guardiana gazety wysokiego ruchu na stronie guardian.co.uk ogłoszono w kwietniu 2011 roku, który został przełączania z Java Scala.
  • The New York Times ujawnił w 2014 roku, że jego wewnętrzny system zarządzania treścią Blackbeard został zbudowany przy użyciu Scala, Akka i Play.
  • Huffington Post gazeta zaczęła zatrudniać Scala jako część systemu dostarczania zawartości Athena w 2013 roku.
  • Szwajcarski bank UBS zatwierdził Scala do ogólnego użytku produkcyjnego.
  • LinkedIn wykorzystuje Scalatra microframework do władzy jego API sygnał.
  • Meetup używa zestawu narzędzi Unfiltered do interfejsów API czasu rzeczywistego.
  • Pamiętaj, że Milk używa zestawu narzędzi Unfiltered, Scala i Akka do publicznego API i aktualizacji w czasie rzeczywistym.
  • Verizon stara się stworzyć „framework nowej generacji” przy użyciu Scali.
  • Airbnb opracowuje oprogramowanie typu open source do uczenia maszynowego „Aerosolve”, napisane w Javie i Scali.
  • Zalando przeniosło swój stos technologiczny z Javy do Scala i Play.
  • SoundCloud wykorzystuje Scala do swojego zaplecza, wykorzystując technologie takie jak Finagle (mikrousługi), Scalding i Spark (przetwarzanie danych).
  • Databricks używa Scali dla platformy Apache Spark Big Data.
  • Morgan Stanley intensywnie korzysta ze Scali w swoich projektach finansowych i związanych z aktywami.
  • W Google i Alphabet Inc. istnieją zespoły, które korzystają ze Scali, głównie dzięki przejęciom, takim jak Firebase i Nest.
  • Walmart Canada używa Scali do swojej platformy zaplecza.
  • Duolingo używa Scali do swojego modułu zaplecza, który generuje lekcje.
  • HMRC używa Scali do wielu wniosków podatkowych rządu Wielkiej Brytanii.
  • M1 Finance używa Scali do swojej platformy zaplecza.

Krytyka

W marcu 2015 r. były wiceprezes grupy Platform Engineering na Twitterze Raffi Krikorian stwierdził, że nie wybrałby Scali w 2011 r. ze względu na jej krzywą uczenia się . W tym samym miesiącu, wiceprezes LinkedIn Kevin Scott, podjął decyzję o „minimalizacji [ich] zależności od Scali”. W listopadzie 2011 r. Yammer odszedł od Scali z powodów, które obejmowały krzywą uczenia się nowych członków zespołu i niezgodność z jednej wersji kompilatora Scala do następnej.

Zobacz też

  • sbt , szeroko stosowane narzędzie do budowania projektów Scala
  • Bawić się! , framework aplikacji internetowych typu open source, który obsługuje Scala
  • Akka , zestaw narzędzi typu open source do tworzenia aplikacji współbieżnych i rozproszonych
  • Chisel , język open-source oparty na Scali, który jest używany do projektowania i generowania sprzętu.

Bibliografia

Dalsza lektura