funkcionálně.cz

Přední český blog o funkcionálním programování, kde se o funkcionálním programování nepíše
««« »»»

Scala - Novinky ve verzi 2.10

24. 5. 2013

Scala toho oproti Javě nabízí mnohem víc: jde o kompaktnější jazyk se silnějším typovým systémem, ideální k psaní DSL a programování na správné úrovni abstrakce (funkcionální programování, OOP, imperativně, deklarativně). Ale kromě toho se Scala vyvíjí drasticky rychlejším tempem. Nová major verze Scaly vychází pravidelně rok od té předchozí a přináší novinky o kterých si v Javě můžeme nechat jenom zdát.

V následujících řádcích si ukážeme novinky, které přinesla Scala 2.10 (momentálně je dostupná verze 2.10.2-RC1).

Value Classes

Třída může nově dědit z předka všech primitivních typů AnyVal. Taková třída je označována za Value Class a chová se jako hodnotový typ (nebo struct z C#). To znamená, že nemusí být nikdy instancována na haldě a kompilátor může vygenerovat kód, do kterého inlinuje vnitřky objektů.

Můžeme tak snadno vytvořit efektivní třídu reprezentující délku:

case class Meter(x: Double) extends AnyVal {
  def + (m: Meter) = x + m.x
  def - (m: Meter) = x - m.x
}

Když třídu použijeme

val m = new Meter(1.0)
val ll = m + new Meter(0.5)
println(ll) // 1.5

Kompilátor přeloží volání metod na statické metody a v co největší míře eliminuje alokace objektů.

val m: Double = 1.0
val ll: Double = Meter.$plus$extension(m, 0.5)

Výsledný kód je stejně efektivní jako kdyby šlo o primitivní typy a zároveň je typově bezpečný. Nemůžeme například sčítat délky v metrickém a imperiálním systému (a ztratit tak další satelit), protože to kompilátor jednoduše nepovolí.

Musíme ale brát v potaz některá omezení JVM. Objekt musí být instancován, pokud ho přiřadíme do proměnné typu Any, jde o generický argument nebo ho chceme uložit do pole. Ale i přesto mám value třídy můžou ušetřit velké množství alokací a znatelně ulehčit GC.

Implicit classes

Další novinkou jsou implicitní třídy, kdy můžeme před definici třídy předřadit klíčové slovo implicit a tím redukovat boilerplate implicitních wrapperů.

Postaru:

class PimpedString(str: String) extends SeqLike[Char] {
  def apply(idx: Int) = str charAt idx
}

implicit pimpString(str: String) = new PimpedString(str)

Nově:

implicit class PimpedString(str: String) extends SeqLike[Char] {
  def apply(idx: Int) = str charAt idx
}

Kompilátor za nás vygeneruje potřebnou implicitní konverzi. Scala tohoto mechanismu využívá natolik, že se i tohle málo počítá (o tom svědčí i fakt, že tento "návrhový vozr" vylepšování existujících objektů dostal svoje jméno - Pimp my library).

Implicitní třídy jsou navržené tak, aby spolupracovaly s value třídami. Když zkombinujeme tyto dvě novinky, dostaneme obdobu extension methods z C#, tedy možnost přidávat funkcionalitu k existujícím třídám bez toho, abychom museli alokovat wrapper.

Futures

Další zásadní novinkou představuje implementace Future ve standardní knihovně.

Future reprezentuje hodnotu, která bude dostupná někdy v budoucnosti. Příkladem může být dotaz do databáze: jeho vykonání trvá určitou dobu, protože s DB serverem musíme komunikovat po síti, ale my nechceme blokovat aktivní vlákno. Proto dostaneme Future, která reprezentuje výsledek dotazu (nebo chybu) až k nám dorazí po síti. Future představují efektivní způsob jaka provádět asynchronní operace a tyto procesy kombinovat a skládat do větších celků a vytvářet komplexní tok programu, který je pořád zcela asynchronní.

Představte si, že máme dvě metody, které komunikují s Twitter API, obě pracují asynchronně a vracejí Future:

def fetchTwitterProfile(screenName: String): Future[User]
def fetchTweets(id: Int): Future[Seq[Tweet]]

Můžeme napsat program, který provádí operace sekvenčně (protože mezi nimi existuje datová závislost), ale stále asynchronně:

val res: Future[(User, Profile)] = for {
  user    <- fetchTwitterProfile(screenName)
  profile <- fetchTweets(user.id)
} yield (user, profile)

Můžeme je provést současně:

val res: Future[(X, Y)] = fetchTwitterProfile(screenName) zip fetchTweets(userId)

Můžeme spustit několik operací a čekat až všechny úspěšně proběhnou:

val usernames = Seq("kaja47", "50shadesofghay", "twitt_vtipy", "stalkr_k47", "stokliku", "robopovidka", "komplet_bible", "stripbot")
val futures: Seq[Future[User]] = usernames map fetchTwitterProfile
val result: Future[Seq[User]] = Future.sequence(futures)

Nebo jich provést několik a použít jenom první odpověď:

val fs: Seq[Future[Res]] = Seq(fetchFrom(server1), fetchFrom(server2), fetchFrom(server3))
val first: Future[Res] = Future.firstCompletedOf(fs)

Důležité je, že všechny tyto složené operace jsou pořád asynchronní a nenutí aktivní vlákno na nic čekat. Pokud potřebujeme na konci zřetězených Future něco udělat a nějak ovlivnit vnější svět, můžeme si zaregistrovat callback, který se zavolá ve chvíli, kdy bude future dokončena nebo skončí chybou.

val userAndTweets: Future[(User, Tweets)] = ???

userAndTweets onComplete {
  case Success((user, tweets)) => println(s"$user tweeted ${tweets.size} tweets")
  case Failure(exception)      => println(s"there was error ($exception)")
}

String Interpolation

String Interpolation je další věc, která působí jenom jako příjemný dodatek jazyka, ale v kombinaci s ostatními novinkami (hlavně makry) je její uplatnění nedozírné. Zjednodušeně řečeno můžeme do stringu začínajícího identifikátorem přímo vkládat proměnné nebo celé výrazy uvozené dolarem.

val what = "awesome"
println(s"string interpolation is ${what.toUpperCase}!")

Typ Dynamic

Dynamic je speciální typ, který se chová dynamicky, pokud na něm zavoláme neexistující metodu.

Tak například x.foo(arg) se přeloží na x.applyDynamic("foo")(arg), pokud je x podtyp Dynamic a nemá metodu foo. To se může hodit například pro tvorbu velice dynamických DSL.

Dále hrají:

Kromě těchto velkých novinek, se kterými se programátor bude potýkat při psaní kódu dnes a denně, nová Scala přináší některé změny pod kapotou, které nejsou na první pohled vidět, a miriádu dalších drobností.

Ve vedlejších rolích

Ale ani to není všechno! Scala 2.10 obsahuje ještě dvě zásadní novinky prozatím označené jako experimentální.

Jednou je reflection API, které nabízí mnohem bohatší informace než prostá Javovská reflexe a pracuje se Scala koncepty. Druhou je systém maker lispovského ražení. Díky nim můžeme ovlivnit kompilaci programu, generovat kód, provádět doménově specifické kontroly nebo jakoukoli jinou magii už v době kompilace. Jde o neuvěřitelně mocný dodatek už beztak silného jazyka. I když je zatím v experimentální fázi, už se objevují zajímavé nástroje, která používají makra například na: přehlednější testy, dotazy ve SLICKu, numerické literály, výuka Scaly, optimizace smyček, inlinování, generování serializačního kódu nebo typově bezpečné SQL.

@kaja47, kaja47@k47.cz, deadbeef.k47.cz, starší články