Kraków, 6 kwietnia 2011 r.

Język programowania Scala.

Referat w ramach seminarium licencjackiego EPI


Spis treści

Etymologia nazwy

Jak sama nazwa wskazuje, Scala była projektowana z myślą o zapewnieniu jak największej skalowalności tworzonych aplikacji. Z jednej strony ma się te aplikacje pisać szybko i przyjemnie, a z drugiej, jeśli w przyszłości okaże się, że muszą one przetwarzać coraz więcej informacji, np. kiedy mamy coraz więcej użytkowników, ma istnieć możliwość przeniesienia takiej aplikacji do chmury, żeby mogła korzystać z większej ilości zasobów sprzętowych. Jest to możliwe dzięki wzorowanemu na Erlangu podejściu do współbieżności, zrealizowanej w oparciu o tzw. aktorów (actors).

O tym, że Scala faktycznie jest skalowalna najlepiej świadczy decyzja portalu społecznościowego Twitter. Mianowicie Twitter, który pierwotnie był napisany w frameworku Ruby on Rails, w 2009 roku poinformował o przepisaniu znacznej części backendu do Scali oraz o tym, że planowalne jest przepisanie również reszty serwisu.

Historia

Historia Scali zaczęła w 2001 roku w szwajcarskiej Politechnice federalnej w Lozannie. Twórcą Scali jest Martin Odersky, który wcześniej współpracował przy tworzeniu kompilatora Javy oraz wsparcia Javy dla programowania uogólnionego. Bez wątpienia fakt ten miał olbrzymi wpływ na rozwój prezentowanego przeze mnie języka, ponieważ po dwóch latach pracy, na przełomie 2003 i 2004 roku, publicznie ukazała się pierwsza wersja Scali, która korzystała właśnie z wirtualnej maszyny Javy. Obecna stabilna wersja ma numer 2.8.1 i jest w bardzo zaawansowanym stadium rozwoju. W styczniu bieżącego roku projekt dostał dofinansowanie w wysokości 200 000 euro od Europejskiej Rady ds. Badań Naukowych. Pieniądze te mają zostać przeznaczone na ulepszanie alternatywnej implementacji przeznaczonej dla środowiska .NET.

Dzięki takiemu podejściu, w którym nowy język bazuje na gotowych środowiskach uruchomieniowych, możliwe jest importowanie wszystkich bibliotek, które zostały na nie stworzone. Tak więc w środowisku JVM możemy wykorzystywać wszystkie biblioteki Javy, a w .NET wszystkie biblioteki języka C#. Ma to zasadnicze znaczenie w rosnącej popularności Scali, ponieważ pisanie nowych bibliotek pochłania bardzo dużo czasu i jest niezwykle kosztowne. Java i C# były tworzone przez wielkie korporacje, które miały na to pieniądze - Sun oraz Microsoft.

Klasyfikacja języka

Statyczna typizacja

Scala jest językiem statycznie typowanym. Oznacza to, że deklarując zmienną musimy podać jej typ (choć wspomaga to opisana poniżej inferencja). Takie podejście pozwala na lepszą optymalizację programu podczas kompilacji oraz łatwiejsze wykrywanie błędów. Przeciwieństwem jest dynamiczna typizacja, stosowana w językach takich jak PHP, JavaScript czy Python.

Silna typizacja

Scala jest językiem silnie typowanym, co oznacza, że każde wyrażenie ma ustalony typ i nie mozna go uzywać w innym kontekście. Na przykład przypisanie zmiennej typu logicznego 0 zamiast false jest niepoprawne. 0 jest literałem typu całkowitego.

Inferencja typów

Mimo że scala jest statycznie typowana, pozwala na pomijanie typów przy inicjalizacji zmiennej, kiedy znany jest typ przypisywanej jej wartości. Np. inicjalizacja "var a=123" jest poprawna i zmienna a automatycznie przyjmuje typ integer. Nie trzeba również podawać typu zwracanego przez funkcję, jeśli jest on jednoznaczny (wszystkie return muszą być tego samego typu).

Wieloparadygmatowość

Scala jest językiem obiektowym. Obiektowość pozwala w sposób naturalny dla człowieka opisywać otaczający go świat. W przeciwieństwie do Javy, Scala jest w pełni obiektowa, w tym znaczeniu, że każde wyrażenie, literał, czy nawet każda funkcja są obiektami. Operatory są natomiast metodami. W Javie mogliśmy zetknąć się np. z typami podstawowymi, które były zaczerpnięte z klasycznych języków strukturalnych - w Scali ich nie ma. Nie ma również pojęć pól i metod statycznych.

Ponadto Scala jest językiem funkcyjnym. Czerpie z takich języków jak Scheme, czy Erlang. Dzięki temu wiele problemów, które mimo swej prostoty wymagają dużej ilości obiektowego kodu i pamięci, może być rozwiązanych w znacznie krótszym czasie z wykorzystaniem funkcji.

Kompilacja, uruchamianie i interpreter

Kod w scali zapisujemy w plikach z rozszerzeniem .scala. Np. Program.scala. Taki kod możemy skompilować wydając komendę:

scalac Program.scala

Następnie skompilowany program możemy uruchomić, podając przy tym ścieżkę do katalogu z klasami:

scala -classpath . Program

Możemy również skorzystać z interpretera, który doskonale nadaje się do nauki podstaw języka. Aby go uruchomić, wpisujemy w terminalu:

scala

Obiektowość

Klasy

W Scali obiektowość oparta jest o klasy. Mogą one posiadać pola (wartości) i metody (funkcje). Ponadto Scala pozwala na dziedziczenie przy użyciu słowa "extends". Warto zauważyć, że konstruktor klasy jest zawarty bezpośrednio w ciele Klasy, a parametry podawane są przy nazwie klasy. W Javie trzeba było stworzyć metodę, która nazywała się tak samo jak klasa.

class NazwaKlasy {
  private val pole = "Witaj świecie!"
  def metoda() = println(pole)
}

val obiekt = new NazwaKlasy
obiekt.metoda

/***********************************/

class Nowa(tekst: String) extends NazwaKlasy {
  println("Konstruktor mówi: " + tekst)
}

val nowy_obiekt = new Nowa("TO JEST TEKST DLA KONSTRUKTORA")
nowy_obiekt.metoda

Obiekty (singletony)

W niektórych językach (chylę czoła JavaScriptowi) stworzenie wzorcowego singletona wymaga dość dużej ilości kodu. W Scali wystarcza jedno słowo - object. Object tworzy jeden obiekt, który może posiadać pola i metody. Od klasy różni go to, że nie można stworzyć większej ilości takich obiektów oraz to, że nie ma konstruktora.

object Singleton {
  private val pole = "Witaj świecie!"
  def metoda() = println(pole)
}

Singleton.metoda

Cechy

Co prawda klasy pozwalały na dziedziczenie, ale czasem chcielibyśmy skorzystać z wzorca dekoratora komponując nową klasę z jakichś istniejących, niezależnych cech. I Scala takie Cechy (traits) posiada. To co odróżnia cechy od znanych z Javy interfejsów, to to, że posiadają one implementację. Krótko mówiąc: cecha = interfejs + implementacja.

class Samochod {
  def powiedz(): Unit = {
    println("To jest samochód!")
  }
}

trait Czerwony {
  def kolor(): String = {
    return "czerwony"
  }
}

trait Szybki {
  def predkosc(): String = {
    return "szybki"
  }
}

class Ferrari extends Samochod with Czerwony with Szybki {
  def opisz() = {
    println("To jest " + predkosc + ", " + kolor + " samochód!")
  }
}

var moj_samochod = new Ferrari

moj_samochod.powiedz
moj_samochod.opisz

samochod_kolegi = new Samochod

samochod_kolegi.powiedz
samochod_kolegi.opisz   // to nie Ferrari!

Funkcyjność

Funkcje

Funkcje definiujemy podając słowo kluczowe "def", nazwę funkcji, a następnie w nawiasach argumenty wraz z określonym po dwukropku typem. Można podać również po dwukropku typ zwracanej wartości (czasem jest to wręcz niezbędne). Prosta funkcja liniowa może wyglądać tak:

def funkcja_liniowa(x:Int) = x+3

a jej wywołanie tak:

funkcja_liniowa(4)

Tworząc bardziej złożone funkcje, ich ciało umieszczamy w klamrach

def funkcja(x:Int, y:Int) = {
  val zmienna = x+3
  println(x + "+3 = " + zmienna)
  return zmienna/y
}

Warto pamiętać, że jeśli nie umieścimy słowa return, funkcja zwróci wartość ostatniego wyrażenia.

Funkcje anonimowe

Scala pozwala na tworzenie funkcji anonimowych, czyli funkcji, które są w pamięci, ale nie mają żadnego identyfikatora, przez który można się do nich odwołać. Przedstawioną wcześniej funkcję liniową możemy zapisać tak:

(x: Int) => x + 3

Funkcje wyższego rzędu

Scala pozwala na tworzenie funkcji wyższego rzędu, czyli można przekazać funkcję jako argument innej funkcji. Należy pamiętać - przekazując funkcję jako argument nie mamy na myśli przekazywania wartości, którą zwraca. Przekazywana jest cała funkcja razem z jej ciałem

def funkcja(x: int, y: int, f: (int, int) => int) = f(x,y)

w powyższym kodzie tworzymy funkcję, która przyjmuje dwie liczby całkowite i jedną funkcję.

Zagnieżdżanie funkcji

Możliwe jest równiez zagnieżdżanie funkcji. Funkcje takie mają zakres lokalny.

def suma(x: Int, y: Int) = {
  var a=x+y
  
  def spierwiastkowana(x: Int) = math.sqrt(x)
  def spotegowana(x: Int) = x*x

  println(spierwiastkowana(a) + " | " + spotegowana(a))
}

Serwisy zrealizowane w języku Scala

Dodatkowe materiały