Úvod do Scaly

1. Úvod

V tomto výučbe sa pozrieme na Scalu - jeden z primárnych jazykov, ktoré bežia na Java Virtual Machine.

Začneme funkciami základného jazyka, ako sú hodnoty, premenné, metódy a riadiace štruktúry. Potom preskúmame niektoré pokročilé funkcie, ako sú funkcie vyššieho rádu, kari, triedy, objekty a porovnávanie vzorov.

Ak chcete získať prehľad o jazykoch JVM, pozrite si nášho rýchleho sprievodcu jazykmi JVM

2. Nastavenie projektu

V tomto tutoriále použijeme štandardnú inštaláciu Scala z //www.scala-lang.org/download/.

Po prvé, pridajme závislosť knižnice scala do nášho pom.xml. Tento artefakt poskytuje štandardnú knižnicu pre jazyk:

 org.scala-lang scala-library 2.12.7 

Po druhé, pridajme scala-maven-plugin na kompiláciu, testovanie, spustenie a dokumentáciu kódu:

 net.alchim31.maven scala-maven-plugin 3.3.2 kompilovať testCompile 

Maven má najnovšie artefakty pre scala-lang a scala-maven-plugin.

Na záver použijeme JUnit na testovanie jednotiek.

3. Základné vlastnosti

V tejto časti preskúmame základné jazykové funkcie na príkladoch. Na tento účel použijeme tlmočník Scala.

3.1. Tlmočník

Tlmočník je interaktívny shell na písanie programov a výrazov.

Vytlačme si pomocou neho „ahoj svet“:

C: \> scala Vitajte na serveri Scala 2.12.6 (Java HotSpot ™ 64-bitový server VM, Java 1.8.0_92). Zadajte výrazy na vyhodnotenie. Alebo vyskúšajte: pomôžte. scala> print („Hello World!“) Hello World! scala>

Hneď ako začneme tlmočník, do príkazového riadku napíšeme „scala“. Spustí sa tlmočník, zobrazí uvítaciu správu a nasleduje výzva.

Potom na túto výzvu napíšeme náš výraz. Tlmočník prečíta výraz, vyhodnotí ho a vytlačí výsledok. Potom sa zacyklí a znova zobrazí výzvu.

Pretože poskytuje okamžitú spätnú väzbu, tlmočník je najjednoduchší spôsob, ako s jazykom začať. Poďme ho preto využiť na preskúmanie základných jazykových funkcií: výrazy a rôzne definície.

3.2. Výrazy

Akýkoľvek vypočítateľný výrok je výraz.

Poďme napísať niekoľko výrazov a pozrieť sa na ich výsledky:

scala> 123 + 321 res0: Int = 444 scala> 7 * 6 res1: Int = 42 scala> "Hello," + "World" res2: String = Hello, World scala> "zipZAP" * 3 res3: String = zipZAPzipZAPzipZAP scala > if (11% 2 == 0) "párne" iné "nepárne" res4: Reťazec = nepárne

Ako vidíme vyššie, každý výraz má hodnotu a typ.

Ak výraz nemá čo vracať, vráti hodnotu typu Jednotka. Tento typ má iba jednu hodnotu: (). Je to podobné ako v prípade neplatný kľúčové slovo v Jave.

3.3. Definícia hodnoty

Kľúčové slovo val sa používa na deklarovanie hodnôt.

Používame ho na pomenovanie výsledku výrazu:

scala> val pi: Double = 3,14 pi: Double = 3,14 scala> print (pi) 3.14 

To nám umožňuje opakovane použiť výsledok.

Hodnoty sú nemenné. Preto ich nemôžeme priradiť:

scala> pi = 3.1415: 12: error: reassignment to val pi = 3.1415 ^

3.4. Premenná definícia

Ak potrebujeme znova priradiť hodnotu, deklarujeme ju namiesto toho ako premennú.

Kľúčové slovo var sa používa na deklaráciu premenných:

scala> var radius: Int = 3 radius: Int = 3

3.5. Definícia metódy

Metódy definujeme pomocou def kľúčové slovo. Po kľúčovom slove zadáme názov metódy, deklarácie parametrov, oddeľovač (dvojbodka) a návratový typ. Potom zadáme oddeľovač (=), za ktorým nasleduje telo metódy.

Na rozdiel od Javy nepoužívame návrat kľúčové slovo vráti výsledok. Metóda vracia hodnotu posledného vyhodnoteného výrazu.

Poďme napísať metódu priem vypočítať priemer z dvoch čísel:

scala> def avg (x: Double, y: Double): Double = {(x + y) / 2} avg: (x: Double, y: Double) Double

Potom vyvolajme túto metódu:

škála> priem. (10,20) res0: dvojnásobok = 12,5 

Ak metóda nemá žiadne parametre, môžeme počas definície a vyvolania vynechať zátvorky. Okrem toho môžeme zložené zátvorky vynechať, ak má telo iba jeden výraz.

Napíšme bezparametrickú metódu hod mincou ktorý náhodne vráti „Hlava“ alebo „Chvost“:

scala> def coinToss = if (Math.random> 0,5) „Head“ else „Tail“ coinToss: String

Ďalej vyvoláme túto metódu:

scala> println (coinToss) Chvost scala> println (coinToss) Hlava

4. Kontrolné štruktúry

Kontrolné štruktúry nám umožňujú meniť tok kontroly v programe. Máme nasledujúce riadiace štruktúry:

  • Výraz if-else
  • Zatiaľ čo slučka a robiť slučku while
  • Na vyjadrenie
  • Skúste výraz
  • Zhodný výraz

Na rozdiel od Javy nemáme ďalej alebo prestávka Kľúčové slová. Máme návrat kľúčové slovo. Mali by sme sa mu však vyhnúť.

Namiesto prepínač vyhlásenie, máme Matching pomocou výrazu zhody. Ďalej môžeme definovať vlastné abstrakcie riadenia.

4.1. keby-inak

The keby-inak výraz je podobný Java. The inak časť je voliteľná. Môžeme vnoriť viac výrazov typu if-else.

Pretože je to výraz, vráti hodnotu. Preto ho používame podobne ako ternárny operátor (? :) v Jave. V skutočnosti jazyk nemá ternárny operátor.

Pomocou if-else napíšme metódu na výpočet najväčšieho spoločného deliteľa:

def gcd (x: Int, y: Int): Int = {if (y == 0) x else gcd (y, x% y)}

Potom napíšeme jednotkový test pre túto metódu:

@Test def whenGcdCalledWith15and27_then3 = {assertEquals (3, gcd (15, 27))}

4.2. Zatiaľ čo Loop

Slučka while má stav a telo. Opakovane vyhodnocuje telo v slučke, kým je podmienka pravdivá - podmienka sa vyhodnocuje na začiatku každej iterácie.

Pretože nemá nič užitočné vrátiť sa, vráti sa Jednotka.

Poďme pomocou cyklu while zapísať metódu na výpočet najväčšieho spoločného deliteľa:

def gcdIter (x: Int, y: Int): Int = {var a = x var b = y while (b> 0) {a = a% b val t = a a = b b = t} a}

Potom overme výsledok:

assertEquals (3, gcdIter (15, 27))

4.3. Robte While Loop

Smyčka do while je podobná cyklu while, až na to, že stav slučky sa vyhodnocuje na konci slučky.

Pomocou slučky do-while napíšme metódu výpočtu faktoriálu:

def faktoriál (a: Int): Int = {var result = 1 var i = 1 do {result * = i i = i + 1} while (i <= a) result}

Ďalej overíme výsledok:

assertEquals (720, faktoriál (6))

4.4. Na vyjadrenie

Výraz for je oveľa univerzálnejší ako cyklus for v jazyku Java.

Môže iterovať v jednej alebo viacerých zbierkach. Okrem toho dokáže filtrovať prvky a vytvárať nové zbierky.

Pomocou výrazu for napíšme metódu na sčítanie rozsahu celých čísel:

def rangeSum (a: Int, b: Int) = {var sum = 0 for (i <- a to b) {sum + = i} sum}

Tu, a až b je generátorový výraz. Generuje sériu hodnôt z a do b.

i <- a až b je generátor. Definuje to i ako val a priradí mu sériu hodnôt produkovaných výrazom generátora.

Telo sa vykoná pre každú hodnotu v rade.

Ďalej overíme výsledok:

assertEquals (55, rangeSum (1, 10))

5. Funkcie

Scala je funkčný jazyk. Funkcie sú tu prvotriedne hodnoty - môžeme ich použiť ako každý iný typ hodnoty.

V tejto časti sa pozrieme na niektoré pokročilé koncepty týkajúce sa funkcií - miestne funkcie, funkcie vyššieho rádu, anonymné funkcie a kari.

5.1. Miestne funkcie

Môžeme definovať funkcie vo vnútri funkcií. Označujú sa ako vnorené funkcie alebo miestne funkcie. Podobne ako lokálne premenné, sú viditeľné iba v rámci funkcie, v ktorej sú definované.

Poďme teraz napísať metódu na výpočet výkonu pomocou vnorenej funkcie:

def power (x: Int, y: Int): Int = {def powNested (i: Int, akumulátor: Int): Int = {if (i <= 0) akumulátor iný powNested (i - 1, x * akumulátor)} powNested (y, 1)}

Ďalej overíme výsledok:

assertEquals (8, sila (2, 3))

5.2. Funkcie vyššieho rádu

Pretože funkcie sú hodnoty, môžeme ich odovzdať ako parametre inej funkcii. Môžeme tiež mať funkciu vrátiť inú funkciu.

Funkcie, ktoré fungujú s funkciami, označujeme ako funkcie vyššieho rádu. Umožňujú nám pracovať na abstraktnejšej úrovni. Pomocou nich môžeme znížiť duplikáciu kódu písaním všeobecných algoritmov.

Poďme teraz napísať funkciu vyššieho rádu, aby sme vykonali mapu a znížili operácie v rozsahu celých čísel:

def mapReduce (r: (Int, Int) => Int, i: Int, m: Int => Int, a: Int, b: Int) = {def iter (a: Int, result: Int): Int = { if (a> b) {result} else {iter (a + 1, r (m (a), result))}} iter (a, i)}

Tu, r a m sú parametre Funkcia typu. Prijatím rôznych funkcií môžeme vyriešiť celý rad problémov, napríklad súčet štvorcov alebo kociek a faktoriál.

Ďalej pomocou tejto funkcie napíšeme ďalšiu funkciu súčet štvorcov ktorý sčíta celé štvorce celých čísel:

@ Test def whenCalledWithSumAndSquare_thenCorrectValue = {def štvorec (x: Int) = x * x def súčet (x: Int, y: Int) = x + y def sumSquares (a: Int, b: Int) = mapReduce (sum, 0, štvorec, a, b) assertEquals (385, sumSquares (1, 10))}

Vyššie to vidíme funkcie vyššieho rádu majú tendenciu vytvárať veľa malých funkcií na jedno použitie. Ich pomenovaniu sa môžeme vyhnúť pomocou anonymných funkcií.

5.3. Anonymné funkcie

Anonymná funkcia je výraz, ktorý sa vyhodnotí ako funkcia. Je to podobné ako výraz lambda v Jave.

Prepíšeme predchádzajúci príklad pomocou anonymných funkcií:

@Test def whenCalledWithAnonymousFunctions_thenCorrectValue = {def sumSquares (a: Int, b: Int) = mapReduce ((x, y) => x + y, 0, x => x * x, a, b) assertEquals (385, sumSquares ( 1, 10))}

V tomto príklade mapReduce prijíma dve anonymné funkcie: (x, y) => x + y a x => x * x.

Scala dokáže odvodiť typy parametrov z kontextu. Preto v týchto funkciách vynechávame typ parametrov.

Výsledkom je stručnejší kód v porovnaní s predchádzajúcim príkladom.

5.4. Funkcie kari

Funkcia curried prijíma viac zoznamov argumentov, ako napr def f (x: Int) (y: Int). Aplikuje sa odovzdaním viacerých zoznamov argumentov, ako je to v f (5) (6).

Vyhodnocuje sa to ako vyvolanie reťazca funkcií. Tieto prechodné funkcie majú jediný argument a vrátia funkciu.

Môžeme tiež čiastočne určiť zoznamy argumentov, ako napr f (5).

Poďme to pochopiť na príklade:

@Test def whenSumModCalledWith6And10_then10 = {// vyčíslená funkcia def sum (f: Int => Int) (a: Int, b: Int): Int = if (a> b) 0 else f (a) + sum (f) (a + 1, b) // iná funkcia curried def mod (n: Int) (x: Int) = x% n // aplikácia funkcie curried assertEquals (1, mod (5) (6)) // čiastočná na použitie funkcie curried je potrebné // použiť podčiarknutie // aby bol typ funkcie explicitný val sumMod5 = sum (mod (5)) _ assertEquals (10, sumMod5 (6, 10))}

Vyššie, súčet a mod každý má dva zoznamy argumentov.

Odovzdáme dva zoznamy argumentov ako mod (5) (6). Toto sa vyhodnotí ako dve volania funkcií. Najprv, mód (5) sa vyhodnotí, ktorá vráti funkciu. To je zase vyvolané argumentom 6. Získame 1 ako výsledok.

Je možné čiastočne použiť parametre ako v mod (5). Výsledkom je funkcia.

Podobne vo výraze suma (mod (5)) _, odovzdávame iba prvý argument súčet funkcie. Preto sumMod5 je funkcia.

Podčiarkovník sa používa ako zástupný symbol pre neuplatnené argumenty. Pretože kompilátor nemôže odvodiť, že sa očakáva typ funkcie, používame koncové podčiarkovník, aby bol explicitný typ návratovej funkcie.

5.5. Parametre podľa mena

Funkcia môže aplikovať parametre dvoma rôznymi spôsobmi - podľa hodnoty a podľa názvu - vyhodnocuje argumenty podľa hodnoty iba raz v čase vyvolania. Naproti tomu vyhodnocuje argumenty podľa mena, kedykoľvek sú odkázané. Ak sa argument o mene nepoužíva, nevyhodnocuje sa.

Scala štandardne používa parametre podľa hodnoty. Ak je pred typom parametra šípka (=>), prepne sa na parameter s menom.

Teraz ho použijeme na implementáciu cyklu while:

def whileLoop (podmienka: => Boolean) (body: => jednotka): jednotka = ak (podmienka) {body whileLoop (podmienka) (telo)}

Aby vyššie uvedená funkcia fungovala správne, obidva parametre stav a telo by sa mali hodnotiť vždy, keď sa na ne odkazuje. Preto ich definujeme ako parametre vedľajšieho mena.

6. Definícia triedy

Triedu definujeme pomocou trieda kľúčové slovo, za ktorým nasleduje názov triedy.

Za menom môžeme určiť primárne parametre konštruktora. Týmto krokom sa do triedy automaticky pridajú členovia s rovnakým menom.

V tele triedy definujeme členy - hodnoty, premenné, metódy atď. Predvolene sú verejné, pokiaľ ich nezmení súkromné alebo chránené modifikátory prístupu.

Musíme použiť prepísať kľúčové slovo prepísať metódu od nadtriedy.

Definujme triedu Zamestnanec:

trieda Zamestnanec (meno valca: String, var plat: Int, yearIncrement: Int = 20) {def incrementSalary (): Unit = {plate + = annualIncrement} override def toString = s "Zamestnanec (meno = meno $, plat = $ plat) ) "}

Tu zadávame tri parametre konštruktora - názov, plata ročnýPrírastok.

Keďže vyhlasujeme názov a plat s val a var kľúčové slová, zodpovedajúci členovia sú verejní. Na druhej strane nepoužívame val alebo var kľúčové slovo pre ročnýPrírastok parameter. Preto je príslušný člen súkromný. Pretože určujeme predvolenú hodnotu pre tento parameter, môžeme ju pri volaní konštruktora vynechať.

Okrem polí definujeme metódu prírastokMzda. Táto metóda je verejná.

Ďalej napíšeme test jednotky pre túto triedu:

@Test def whenSalaryIncremented_thenCorrectSalary = {val zamestnanec = nový zamestnanec ("John Doe", 1 000) employee.incrementSalary () assertEquals (1020, employee.salary)}

6.1. Abstraktná trieda

Používame kľúčové slovo abstraktné aby bol kurz abstraktný. Je to podobné ako v Jave. Môže mať všetkých členov, ktorých môže mať bežná trieda.

Ďalej môže obsahovať abstraktné členy. Jedná sa o členov s iba deklaráciou a bez definície, ktorých definícia je uvedená v podtriede.

Podobne ako v jazyku Java nemôžeme vytvoriť inštanciu abstraktnej triedy.

Poďme si teraz ilustrovať abstraktnú triedu na príklade.

Najskôr si vytvorme abstraktnú triedu IntSet reprezentovať množinu celých čísel:

abstraktná trieda IntSet {// pridanie prvku do množiny def incl (x: Int): IntSet // to, či prvok patrí do množiny def obsahuje (x: Int): logická hodnota}

Ďalej vytvoríme konkrétnu podtriedu EmptyIntSet reprezentovať prázdnu množinu:

trieda EmptyIntSet rozširuje IntSet {def obsahuje (x: Int) = false def incl (x: Int) = nový NonEmptyIntSet (x, toto)}

Potom ďalšia podtrieda NonEmptyIntSet predstavujú neprázdne množiny:

class NonEmptyIntSet (val head: Int, val tail: IntSet) extends IntSet {def contains (x: Int) = head == x || (chvost obsahuje x) def incl (x: Int) = if (this contains x) {this} else {new NonEmptyIntSet (x, this)}}

Na záver napíšeme jednotkový test pre NonEmptySet:

@Test def givenSetOf1To10_whenContains11Called_thenFalse = {// Nastaviť množinu obsahujúcu celé čísla od 1 do 10. val set1To10 = Rozsah (1, 10) .foldLeft (nový EmptyIntSet (): IntSet) {(x, y) => x vrátane y} assertFalse (set1To10 obsahuje 11)}

6.2. Rysy

Vlastnosti zodpovedajú rozhraniam Java s týmito rozdielmi:

  • schopný vystúpiť z triedy
  • má prístup k členom nadtriedy
  • môže mať inicializačné vyhlásenia

Definujeme ich tak, ako definujeme triedy, ale pomocou znaku vlastnosť kľúčové slovo. Okrem toho môžu mať rovnakých členov ako abstraktné triedy, okrem parametrov konštruktora. Ďalej sú určené na pridanie do inej triedy ako mixin.

Teraz si ukážme vlastnosti na príklade.

Najskôr si definujme vlastnosť UpperCasePrinter zabezpečiť natiahnuť metóda vracia hodnotu veľkým písmenom:

vlastnosť UpperCasePrinter {prepísať def toString = super.toString toUpperCase}

Potom otestujme túto vlastnosť pridaním do znaku Zamestnanec trieda:

@Test def givenEmployeeWithTrait_whenToStringCalled_thenUpper = {val zamestnanec = nový zamestnanec ("John Doe", 10) s UpperCasePrinter assertEquals ("EMPLOYEE (NAME = JOHN DOE, SALARY = 10)", employee.toString)}

Triedy, objekty a vlastnosti môžu dediť najviac jednu triedu, ale ľubovoľný počet vlastností.

7. Definícia objektu

Objekty sú inštanciami triedy. Ako sme videli v predchádzajúcich príkladoch, objekty z triedy vytvárame pomocou znaku Nový kľúčové slovo.

Ak však môže mať trieda iba jednu inštanciu, musíme zabrániť vytváraniu viacerých inštancií. V Jave to dosiahneme použitím Singletonovho vzoru.

Pre takéto prípady máme stručnú syntax nazývanú definícia objektu - podobná definícii triedy s jedným rozdielom. Namiesto použitia trieda kľúčové slovo, používame objekt kľúčové slovo. Týmto krokom definujete triedu a lenivo vytvoríte jej jedinú inštanciu.

Definície objektov používame na implementáciu úžitkových metód a singletónov.

Definujme a Utility objekt:

Object Utils {def average (x: Double, y: Double) = (x + y) / 2}

Tu definujeme triedu Utility a tiež vytvorenie jeho jedinej inštancie.

Na túto jedinú inštanciu odkazujeme pomocou jej názvuUtility. Táto inštancia sa vytvorí pri prvom prístupe.

Nemôžeme vytvoriť ďalšiu inštanciu Utils pomocou Nový kľúčové slovo.

Teraz napíšeme jednotkový test pre Utility objekt:

assertEquals (15.0, Utils.average (10, 20), 1e-5)

7.1. Sprievodný objekt a sprievodná trieda

Ak majú definícia triedy a objektu rovnaký názov, nazveme ich ako sprievodná trieda a sprievodný objekt. Musíme definovať obe v rovnakom súbore. Sprievodné objekty majú prístup k súkromným členom z ich sprievodnej triedy a naopak.

Na rozdiel od Javy nemáme statických členov. Namiesto toho na implementáciu statických členov používame sprievodné objekty.

8. Zhoda vzorov

Priraďovanie vzorov spája výraz s postupnosťou alternatív. Každá z nich začína kľúčovým slovom prípade. Nasleduje vzor, ​​šípka oddeľovača (=>) a množstvo výrazov. Výraz sa vyhodnotí, ak sa vzor zhoduje.

Môžeme vytvárať vzory z:

  • konštruktory triedy prípadov
  • variabilný vzor
  • vzor zástupných znakov _
  • literály
  • konštantné identifikátory

Triedy prípadov uľahčujú porovnávanie vzorov na objektoch. Pridáme prípade kľúčové slovo pri definovaní triedy, aby sa z neho stala prípadová trieda.

Preto je porovnávanie vzorov oveľa výkonnejšie ako vyhlásenie switch v Jave. Z tohto dôvodu je to často používaná jazyková funkcia.

Teraz napíšeme Fibonacciho metódu pomocou porovnávania vzorov:

def fibonacci (n: Int): Int = n match 1 => 1 prípad x if x> 1 => fibonacci (x-1) + fibonacci (x-2) 

Ďalej napíšeme jednotkový test pre túto metódu:

assertEquals (13, fibonacci (6))

9. Záver

V tomto návode sme predstavili jazyk Scala a niektoré z jeho kľúčových funkcií. Ako sme videli, poskytuje vynikajúcu podporu pre imperatívne, funkčné a objektovo orientované programovanie.

Celý zdrojový kód nájdete ako obvykle na GitHub.