Tanulja meg a Scalát 0–60-ig: Az alapok

A Scala egy általános célú, magas szintű programozási nyelv, amely egyensúlyt kínál a funkcionális és az objektum-orientált programok fejlesztése között.

Miről szól a funkcionális programozás? Egyszerűen fogalmazva: a függvények a funkcionális programozás első osztályú polgárai. Annak érdekében, hogy kibővítsük a program alapvető funkcióit, hajlamosak vagyunk további osztályokat írni, amelyek bizonyos irányelveken / interfészeken alapulnak. A funkcionális programozásban a függvények segítenek elérni ugyanezt.

Minden magyarázathoz a Scala REPL szolgáltatást használjuk. Nagyon hasznos és informatív eszköz a Scala elsajátításához. Aranyos kis üzeneteket naplóz a kódunk értelmezéséről és végrehajtásáról.

Kezdjük először az alapokkal.

1. Változók

Meghatározhatunk változhatatlan változókat a következők használatával val:

scala> val name = "King"name: String = King

A változtatható változók meghatározhatók és módosíthatók var:

scala> var name = "King"name: String = King
scala> name = "Arthur"name: String = Arthur

Az általunk használt defrendelni egy címkét megváltoztathatatlan érték, amelynek értékelése elhalasztják egy későbbi időpontban. Ez azt jelenti, hogy a címke értékét lustán értékelik minden használatkor.

scala> var name = "King"name: String = King
scala> def alias = namealias: String
scala> aliasres2: String = King

Megfigyeltél valami érdekeset?

A meghatározás aliassorán nem rendeltek értéket, alias: Stringmivel az lustán társul, amikor hivatkozunk rá. Mi történne, ha megváltoztatnánk az értékét name?

scala> aliasres5: String = King
scala> name = "Arthur, King Arthur"name: String = Arthur, King Arthur
scala> aliasres6: String = Arthur, King Arthur

2. Ellenőrizze az áramlást

Vezérlési folyamat utasításokat használunk a döntési logikánk kifejezésére.

if-elseNyilatkozatot írhat az alábbiak szerint:

if(name.contains("Arthur")) { print("Entombed sword")} else { print("You're not entitled to this sword")}

Vagy használhatja while:

var attempts = 0while (attempts < 3) { drawSword() attempts += 1}

3. Gyűjtemények

A Scala kifejezetten megkülönbözteti a megváltoztathatatlan és a változtatható gyűjteményeket - közvetlenül a csomag névterétől ( scala.collection.immutablevagy scala.collection.mutable).

A megváltoztathatatlan gyűjteményektől eltérően a változtatható gyűjtemények a helyükön frissíthetők vagy bővíthetők. Ez lehetővé teszi számunkra, hogy mellékhatásként elemeket változtassunk, adjunk hozzá vagy távolítsunk el.

De a hozzáadási, eltávolítási vagy frissítési műveletek megváltoztathatatlan gyűjtemények esetén egy új gyűjteményt ad vissza.

Változatlan gyűjtemények mindig automatikusan importálhatók az scala._ (amely szintén tartalmaz álnevet scala.collection.immutable.List).

A mutábilis gyűjtemények használatához azonban kifejezetten importálnia kell scala.collection.mutable.List.

A funkcionális programozás szellemében példáinkat elsősorban a nyelv változhatatlan aspektusaira alapozzuk, kisebb kitérőkkel a mutálható oldalra.

Lista

Különféle módon hozhatunk létre listát:

scala> val names = List("Arthur", "Uther", "Mordred", "Vortigern")
names: List[String] = List(Arthur, Uther, Mordred, Vortigern)

Egy másik praktikus megközelítés egy lista meghatározása a hátrányok ::operátor segítségével. Ez összekapcsol egy fej elemet a lista fennmaradó farkaival.

scala> val name = "Arthur" :: "Uther" :: "Mordred" :: "Vortigern" :: Nil
name: List[String] = List(Arthur, Uther, Mordred, Vortigern)

Ami egyenértékű:

scala> val name = "Arthur" :: ("Uther" :: ("Mordred" :: ("Vortigern" :: Nil)))
name: List[String] = List(Arthur, Uther, Mordred, Vortigern)

A listaelemekhez közvetlenül az indexük alapján férhetünk hozzá. Ne feledje, hogy a Scala nulla alapú indexelést használ:

scala> name(2)
res7: String = Mordred

Néhány általános segítő módszer a következőket tartalmazza:

list.head, amely az első elemet adja vissza:

scala> name.head
res8: String = Arthur

list.tail, amely egy lista farkát adja vissza (amely a fej kivételével mindent tartalmaz):

scala> name.tail
res9: List[String] = List(Uther, Mordred, Vortigern)

Készlet

Setlehetővé teszi, hogy nem ismétlődő entitáscsoportot hozzunk létre. Listalapértelmezés szerint nem szünteti meg a duplikátumokat.

scala> val nameswithDuplicates = List("Arthur", "Uther", "Mordred", "Vortigern", "Arthur", "Uther")
nameswithDuplicates: List[String] = List(Arthur, Uther, Mordred, Vortigern, Arthur, Uther)

Itt kétszer megismétlik az „Arthurt”, és az „Uthert” is.

Hozzunk létre egy azonos nevű halmazot. Figyelje meg, hogyan zárja ki az ismétlődéseket.

scala> val uniqueNames = Set("Arthur", "Uther", "Mordred", "Vortigern", "Arthur", "Uther")
uniqueNames: scala.collection.immutable.Set[String] = Set(Arthur, Uther, Mordred, Vortigern)

A Set segítségével ellenőrizhetjük, hogy létezik-e egy adott elem contains():

scala> uniqueNames.contains("Vortigern")res0: Boolean = true

A + metódus segítségével elemeket adhatunk egy halmazhoz (amely varargsváltozó hosszúságú argumentumokat vesz fel )

scala> uniqueNames + ("Igraine", "Elsa", "Guenevere")res0: scala.collection.immutable.Set[String] = Set(Arthur, Elsa, Vortigern, Guenevere, Mordred, Igraine, Uther)

Hasonlóképpen eltávolíthatunk elemeket a -módszerrel

scala> uniqueNames - "Elsa"
res1: scala.collection.immutable.Set[String] = Set(Arthur, Uther, Mordred, Vortigern)

Térkép

Mapegy iterálható gyűjtemény, amely leképezéseket tartalmaz az keyelemektől a megfelelő elemekig value, amelyek így hozhatók létre:

scala> val kingSpouses = Map( | "King Uther" -> "Igraine", | "Vortigern" -> "Elsa", | "King Arthur" -> "Guenevere" | )
kingSpouses: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere)

A térkép egy adott kulcsának értékei a következőképpen érhetők el:

scala> kingSpouses("Vortigern")res0: String = Elsa

Hozzáadhatunk egy bejegyzést a Térképhez a következő +módszerrel:

scala> kingSpouses + ("Launcelot" -> "Elaine")res0: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere, Launcelot -> Elaine)

Meglévő leképezés módosításához egyszerűen hozzá kell adnunk a frissített kulcsértéket:

scala> kingSpouses + ("Launcelot" -> "Guenevere")res1: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere, Launcelot -> Guenevere)

Vegye figyelembe, hogy mivel a gyűjtemény megváltoztathatatlan, minden szerkesztési művelet egy új gyűjteményt ( res0, res1) ad vissza az alkalmazott módosításokkal. Az eredeti gyűjtemény kingSpousesváltozatlan marad.

4. Funkcionális kombinátorok

Most, hogy megtanultuk az entitások halmazának csoportosítását, nézzük meg, hogyan használhatjuk a funkcionális kombinátorokat értelmes átalakítások létrehozására az ilyen gyűjteményekben.

John Hughes egyszerű szavaival:

A kombinátor egy olyan funkció, amely programrészleteket épít programrészletekből.

An in-depth look at how combinators work is outside of this article’s scope. But, we’ll try to touch upon a high-level understanding of the concept anyhow.

Let’s take an example.

Suppose we want to find names of all queens using the kingSpouses collection map that we created.

We’d want to do something along the lines of examining each entry in the map. If the key has the name of a king, then we’re interested in the name of it’s spouse (i.e. queen).

We shall use the filter combinator on map, which has a signature like:

collection.filter( /* a filter condition method which returns true on matching map entries */)

Overall we shall perform the following steps to find queens:

  • Find the (key, value) pairs with kings’ names as keys.
  • Extract the values (names of queen) only for such tuples.

The filter is a function which, when given a (key, value), returns true / false.

  1. Find the map entries pertaining to kings.

Let’s define our filtering predicate function. Since key_value is a tuple of (key, value), we extract the key using ._1 (and guess what ._2 returns?)

scala> def isKingly(key_value: (String, String)): Boolean = key_value._1.toLowerCase.contains("king")
isKingly: (key_value: (String, String))Boolean

Now we shall use the filter function defined above to filter kingly entries.

scala> val kingsAndQueens = kingSpouses.filter(isKingly)
kingsAndQueens: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, King Arthur -> Guenevere)

2. Extract the names of respective queens from the filtered tuples.

scala> kingsAndQueens.values
res10: Iterable[String] = MapLike.DefaultValuesIterable(Igraine, Guenevere)

Let’s print out the names of queens using the foreach combinator:

scala> kingsAndQueens.values.foreach(println)IgraineGuenevere

Some other useful combinators are foreach, filter, zip, partition, find.

We shall re-visit some of these after having learnt how to define functions and passing functions as arguments to other functions in higher-order functions.

Let’s recap on what we’ve learned:

  • Different ways of defining variables
  • Various control-flow statements
  • Some basics about various collections
  • Overview of using functional combinators on collections

I hope you found this article useful. It is first in a series of articles to follow on learning Scala.

In part two, we’ll learn about defining classes, traits, encapsulation and other object-oriented concepts.

Please feel free to let me know your feedback and suggestions on how I can improve the content. Until then, ❤ coding.