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 (před 6 lety) — k47

Scala toho oproti Javě nabízí mnohem víc: jde o kom­pakt­nější jazyk se sil­něj­ším ty­po­vým sys­té­mem, ide­ální k psaní DSL a pro­gra­mo­vání na správné úrovni abs­trakce (funk­ci­o­nální pro­gra­mo­vání, OOP, im­pe­ra­tivně, de­kla­ra­tivně). Ale kromě toho se Scala vyvíjí dras­ticky rych­lej­ším tempem. Nová major verze Scaly vy­chází pra­vi­delně rok od té před­chozí a při­náší no­vinky o kte­rých si v Javě můžeme nechat jenom zdát.

V ná­sle­du­jí­cích řád­cích si uká­žeme no­vinky, které při­nesla Scala 2.10 (mo­men­tálně je do­stupná verze 2.10.2-RC1).

Value Clas­ses

Třída může nově dědit z předka všech pri­mi­tiv­ních typů AnyVal. Taková třída je ozna­čo­vána za Value Class a chová se jako hod­no­tový typ (nebo struct z C#). To zna­mená, že nemusí být nikdy in­stan­co­vána na haldě a kom­pi­lá­tor může vy­ge­ne­ro­vat kód, do kte­rého in­li­nuje vnitřky ob­jektů.

Můžeme tak snadno vy­tvo­řit efek­tivní třídu re­pre­zen­tu­jí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 po­u­ži­jeme

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

Kom­pi­lá­tor pře­loží volání metod na sta­tické metody a v co nej­větší míře eli­mi­nuje alo­kace ob­jektů.

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

Vý­sledný kód je stejně efek­tivní jako kdyby šlo o pri­mi­tivní typy a zá­ro­veň je typově bez­pečný. Ne­mů­žeme na­pří­klad sčítat délky v me­t­ric­kém a im­pe­ri­ál­ním sys­tému (a ztra­tit tak další sa­te­lit), pro­tože to kom­pi­lá­tor jed­no­duše ne­po­volí.

Musíme ale brát v potaz ně­která ome­zení JVM. Objekt musí být in­stan­co­ván, pokud ho při­řa­díme do pro­měnné typu Any, jde o ge­ne­rický ar­gu­ment nebo ho chceme uložit do pole. Ale i přesto mám value třídy můžou ušet­řit velké množ­ství alo­kací a zna­telně uleh­čit GC.

Im­pli­cit clas­ses

Další no­vin­kou jsou im­pli­citní třídy, kdy můžeme před de­fi­nici třídy před­řa­dit klí­čové slovo implicit a tím re­du­ko­vat bo­i­ler­plate im­pli­cit­ních wrap­perů.

Po­staru:

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
}

Kom­pi­lá­tor za nás vy­ge­ne­ruje po­třeb­nou im­pli­citní kon­verzi. Scala tohoto me­cha­nismu vy­u­žívá na­to­lik, že se i tohle málo počítá (o tom svědčí i fakt, že tento „ná­vr­hový vozr“ vy­lep­šo­vání exis­tu­jí­cích ob­jektů dostal svoje jméno – Pimp my lib­rary).

Im­pli­citní třídy jsou na­vr­žené tak, aby spo­lu­pra­co­valy s value tří­dami. Když zkom­bi­nu­jeme tyto dvě no­vinky, do­sta­neme obdobu ex­tension me­thods z C#, tedy mož­nost při­dá­vat funk­ci­o­na­litu k exis­tu­jí­cím třídám bez toho, abychom museli alo­ko­vat wrap­per.

Fu­tu­res

Další zá­sadní no­vin­kou před­sta­vuje im­ple­men­tace Future ve stan­dardní knihovně.

Future re­pre­zen­tuje hod­notu, která bude do­stupná někdy v bu­douc­nosti. Pří­kla­dem může být dotaz do da­ta­báze: jeho vy­ko­nání trvá ur­či­tou dobu, pro­tože s DB ser­ve­rem musíme ko­mu­ni­ko­vat po síti, ale my ne­chceme blo­ko­vat ak­tivní vlákno. Proto do­sta­neme Future, která re­pre­zen­tuje vý­sle­dek dotazu (nebo chybu) až k nám dorazí po síti. Future před­sta­vují efek­tivní způsob jaka pro­vá­dět asyn­chronní ope­race a tyto pro­cesy kom­bi­no­vat a sklá­dat do vět­ších celků a vy­tvá­řet kom­plexní tok pro­gramu, který je pořád zcela asyn­chronní.

Před­stavte si, že máme dvě metody, které ko­mu­ni­kují s Twit­ter API, obě pra­cují asyn­chronně a vra­cejí Future:

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

Můžeme napsat pro­gram, který pro­vádí ope­race sek­venčně (pro­tože mezi nimi exis­tuje datová zá­vis­lost), ale stále asyn­chronně:

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

Můžeme je pro­vést sou­časně:

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

Můžeme spus­tit ně­ko­lik ope­rací a čekat až všechny úspěšně pro­běh­nou:

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 pro­vést ně­ko­lik a použít jenom první od­po­věď:

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é ope­race jsou pořád asyn­chronní a nenutí ak­tivní vlákno na nic čekat. Pokud po­tře­bu­jeme na konci zře­tě­ze­ných Future něco udělat a nějak ovliv­nit vnější svět, můžeme si za­re­gis­tro­vat call­back, který se zavolá ve chvíli, kdy bude future do­kon­č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 In­ter­po­lation

String In­ter­po­lation je další věc, která působí jenom jako pří­jemný do­da­tek jazyka, ale v kom­bi­naci s ostat­ními no­vin­kami (hlavně makry) je její uplat­nění ne­do­zírné. Zjed­no­du­šeně řečeno můžeme do stringu za­čí­na­jí­cího iden­ti­fi­ká­to­rem přímo vklá­dat pro­měnné nebo celé výrazy uvo­zené do­la­rem.

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

Typ Dy­na­mic

Dy­na­mic je spe­ci­ální typ, který se chová dy­na­micky, pokud na něm za­vo­láme ne­e­xis­tu­jící metodu.

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

Dále hrají:

Kromě těchto vel­kých no­vi­nek, se kte­rými se pro­gra­má­tor bude po­tý­kat při psaní kódu dnes a denně, nová Scala při­náší ně­které změny pod ka­po­tou, které nejsou na první pohled vidět, a mi­ri­ádu dal­ších drob­ností.

Ve ve­d­lej­ších rolích

Ale ani to není všechno! Scala 2.10 ob­sa­huje ještě dvě zá­sadní no­vinky pro­za­tím ozna­čené jako ex­pe­ri­men­tální.

Jednou je re­flection API, které nabízí mnohem bo­hatší in­for­mace než prostá Ja­vov­ská re­flexe a pra­cuje se Scala kon­cepty. Druhou je systém maker lis­pov­ského ražení. Díky nim můžeme ovliv­nit kom­pi­laci pro­gramu, ge­ne­ro­vat kód, pro­vá­dět do­mé­nově spe­ci­fické kon­t­roly nebo ja­kou­koli jinou magii už v době kom­pi­lace. Jde o ne­u­vě­ři­telně mocný do­da­tek už beztak sil­ného jazyka. I když je zatím v ex­pe­ri­men­tální fázi, už se ob­je­vují za­jí­mavé ná­stroje, která po­u­ží­vají makra na­pří­klad na: pře­hled­nější testy, dotazy ve SLICKu, nu­me­rické li­te­rály, výuka Scaly, op­ti­mi­zace smyček, in­li­no­vání, ge­ne­ro­vání se­ri­a­li­zač­ního kódu nebo typově bez­pečné SQL.

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