Típusórák a Scala3-ban: Útmutató kezdőknek | Főkönyv

Típusórák a Scala3-ban: Útmutató kezdőknek | Főkönyv

Típusórák a Scala3-ban: Útmutató kezdőknek | Ledger PlatoBlockchain adatintelligencia. Függőleges keresés. Ai.

Ez a dokumentum a kezdő Scala3 fejlesztőknek szól, akik már járatosak a Scala prózában, de értetlenül állnak minden `implicits` és paraméterezett tulajdonságok a kódban.

Ez a dokumentum elmagyarázza, miért, hogyan, hol és mikor Típusosztályok (TC).

A dokumentum elolvasása után a kezdő Scala3 fejlesztő komoly ismereteket szerez a használatához, és belemerül a forráskódba nagyon a Scala könyvtárak közül, és kezdje el írni az idiomatikus Scala kódot.

Kezdjük azzal, hogy miért…

A kifejezési probléma

A 1998, Philip Wadler kijelentette hogy „a kifejezési probléma egy régi probléma új neve”. Ez a szoftver bővíthetőség problémája. Wadler úr szerint a kifejezési probléma megoldásának meg kell felelnie a következő szabályoknak:

  • 1. szabály: Engedélyezze a végrehajtását meglévő viselkedések (gondoljunk a Scala-vonásra), amelyre alkalmazni kell új ábrázolások (gondolj egy esetosztályra)
  • 2. szabály:  Engedélyezze a végrehajtását új viselkedések kell alkalmazni meglévő reprezentációk
  • 3. szabály: Nem veszélyeztetheti a típusú biztonság
  • 4. szabály: Nem szükséges újrafordítani meglévő kódot

A probléma megoldása lesz ennek a cikknek az ezüstszála.

1. szabály: a meglévő viselkedés megvalósítása új ábrázoláson

Bármely objektumorientált nyelv rendelkezik beépített megoldással az 1. szabályhoz altípus polimorfizmus. Bármelyik `-t nyugodtan megvalósíthatjatrait` egy `-től való függésben van meghatározvaclass` a saját kódjában, a függőség újrafordítása nélkül. Lássuk működés közben:

Scala

def todo = 42
type Height = Int
type Block = Int

object Lib1:
 trait Blockchain:
 def getBlock(height: Height): Block

 case class Ethereum() extends Blockchain:
 override def getBlock(height: Height) = todo

 case class Bitcoin() extends Blockchain:
 override def getBlock(height: Height) = todo


object Lib2:
 import Lib1.*

 case class Polkadot() extends Blockchain:
 override def getBlock(height: Height): Block = todo

val eth = Lib1.Ethereum()
val btc = Lib1.Bitcoin()
val dot = Lib2.Polkadot()

Ebben a fiktív példában a library `Lib1` (5. sor) egy tulajdonságot határoz megBlockchain` (6. sor) annak 2 megvalósításával (9. és 12. sor). `Lib1` változatlan marad ebben a dokumentumban (a 4. szabály érvényesítése).

`Lib2` (15. sor) a meglévő viselkedést valósítja meg `Blockchain`egy új osztályon`Polkadot` (1. szabály) típusbiztonságos (3. szabály) módon, újrafordítás nélkülLib1` (4. szabály). 

2. szabály: a meglévő reprezentációkra alkalmazandó új viselkedések megvalósítása

Képzeljük el `-banLib2`új viselkedést akarunk`lastBlock` minden egyes esetében külön kell végrehajtaniBlockchain`.

Az első dolog, ami eszünkbe jut, egy nagy kapcsoló létrehozása a paraméter típusa alapján.

Scala

def todo = 42
type Height = Int
type Block = Int

object Lib1:
 trait Blockchain:
 def getBlock(height: Height): Block

 case class Ethereum() extends Blockchain:
 override def getBlock(height: Height) = todo

 case class Bitcoin() extends Blockchain:
 override def getBlock(height: Height) = todo

object Lib2:
 import Lib1.*

 case class Polkadot() extends Blockchain:
 override def getBlock(height: Height): Block = todo

 def lastBlock(blockchain: Blockchain): Block = blockchain match
 case _:Ethereum => todo
 case _:Bitcoin => todo
 case _:Polkadot => todo
 

object Lib3:
 import Lib1.*

 case class Polygon() extends Blockchain:
 override def getBlock(height: Height): Block = todo

import Lib1.*, Lib2.*, Lib3.*
println(lastBlock(Bitcoin()))
println(lastBlock(Ethereum()))
println(lastBlock(Polkadot()))
println(lastBlock(Polygon()))

Ez a megoldás a típusalapú polimorfizmus gyenge reimplementációja, amely már be van sütött nyelven!

`Lib1` érintetlenül marad (ne feledje, a 4-es szabály érvényesül az egész dokumentumban). 

A `Lib2` van rendben van amíg egy újabb blokkláncot be nem vezetnek a `-baLib3`. Sérti a típusbiztonsági szabályt (3. szabály), mert ez a kód futás közben meghibásodik a 37. sorban. És módosítja a `Lib2` sértené a 4. szabályt.

Egy másik megoldás egy `extension`.

Scala

def todo = 42
type Height = Int
type Block = Int

object Lib1:
 trait Blockchain:
 def getBlock(height: Height): Block

 case class Ethereum() extends Blockchain:
 override def getBlock(height: Height) = todo

 case class Bitcoin() extends Blockchain:
 override def getBlock(height: Height) = todo

object Lib2:
 import Lib1.*

 case class Polkadot() extends Blockchain:
 override def getBlock(height: Height): Block = todo

 def lastBlock(): Block = todo

 extension (eth: Ethereum) def lastBlock(): Block = todo

 extension (btc: Bitcoin) def lastBlock(): Block = todo

import Lib1.*, Lib2.*
println(Bitcoin().lastBlock())
println(Ethereum().lastBlock())
println(Polkadot().lastBlock())

def polymorphic(blockchain: Blockchain) =
 // blockchain.lastBlock()
 ???

`Lib1` érintetlenül marad (a 4. szabály érvényesítése a teljes dokumentumban). 

`Lib2` határozza meg a viselkedést a típusának (21. sor), a `kiterjesztésnek' pedig a meglévő típusoknak (23. és 25. sor).

28-30. sorok, az új viselkedés minden osztályban használható. 

De nincs mód arra, hogy ezt az új viselkedést polimorf módon nevezzük (32. sor). Minden erre irányuló kísérlet fordítási hibákhoz (33. sor) vagy típusalapú kapcsolókhoz vezet. 

Ez a 2. szabály trükkös. Megpróbáltuk megvalósítani a saját polimorfizmus-definíciónkkal és "kiterjesztési" trükkünkkel. És ez furcsa volt.

Hiányzik egy darab ún ad-hoc polimorfizmus: a viselkedés megvalósításának biztonságos elküldésének képessége egy típus szerint, bárhol is van a viselkedés és típus meghatározva. Írd be a Type Class mintát.

A típusosztály mintája

A Type Class (röviden TC) minta receptje 3 lépésből áll. 

  1. Határozzon meg egy új viselkedést
  2. Végezze el a viselkedést
  3. Használd a viselkedést

A következő részben a legegyszerűbb módon valósítom meg a TC mintát. Bőbeszédű, nehézkes és nem praktikus. De várjon, ezeket a figyelmeztetéseket lépésről lépésre rögzítjük a dokumentumban.

1. Határozzon meg egy új viselkedést
Scala

object Lib2:
 import Lib1.*

 trait LastBlock[A]:
 def lastBlock(instance: A): Block

`Lib1` ismét érintetlenül maradt.

Az új viselkedés is a TC a tulajdonság által materializálódott. A tulajdonságban definiált függvények a viselkedés egyes aspektusainak alkalmazását jelentik.

A `AA ` azt a típust jelöli, amelyre a viselkedést alkalmazni szeretnénk, és amelyek a ` altípusaiBlockchain` esetünkben.

Néhány megjegyzés:

  • Ha szükséges, a paraméterezett `A` tovább korlátozható a Scala típusú rendszerrel. Például érvényesíthetjük a `A` lenniBlockchain`. 
  • Ezenkívül a TC sokkal több funkciót tartalmazhat.
  • Végül minden függvénynek sok tetszőleges paramétere lehet.

De az olvashatóság kedvéért tegyük egyszerűvé a dolgokat.

2. Valósítsa meg a viselkedést
Scala

object Lib2:
 import Lib1.*

 trait LastBlock[A]:
 def lastBlock(instance: A): Block

 val ethereumLastBlock = new LastBlock[Ethereum]:
 def lastBlock(eth: Ethereum) = eth.lastBlock

 val bitcoinLastBlock = new LastBlock[Bitcoin]:
 def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")

Minden típushoz az új `LastBlock` viselkedés várható, ennek a viselkedésnek van egy konkrét példája. 

A `Ethereum` a 22-es megvalósítási sor a `-ből kerül kiszámításraeth` példány paraméterként átadva. 

A `LastBlock` for `BitcoinA 25. sor nem felügyelt IO-val van megvalósítva, és nem használja a paraméterét.

Szóval, `Lib2`új viselkedést valósít meg`LastBlock` for `Lib1` osztályok.

3. Használja a viselkedést
Scala

object Lib2:
 import Lib1.*

 trait LastBlock[A]:
 def lastBlock(instance: A): Block

 val ethereumLastBlock = new LastBlock[Ethereum]:
 def lastBlock(eth: Ethereum) = eth.lastBlock

 val bitcoinLastBlock = new LastBlock[Bitcoin]:
 def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")

import Lib1.*, Lib2.*

def useLastBlock[A](instance: A, behavior: LastBlock[A]) =
 behavior.lastBlock(instance)

println(useLastBlock(Ethereum(lastBlock = 2), ethereumLastBlock))
println(useLastBlock(Bitcoin(), bitcoinLastBlock))

30. soruseLastBlock` a ` példányát használjaA` és a `LastBlock` az adott példányhoz meghatározott viselkedés.

33. soruseLastBlockA ` a ` példányával kerül meghívásraEthereum` és a ` megvalósításaLastBlock`-ben van meghatározvaLib2`. Vegye figyelembe, hogy a ` bármely alternatív megvalósítása átadhatóLastBlock[A]` (gondolj rá függőségi injekció).

`useLastBlock` a ragasztó a reprezentáció (a tényleges A) és viselkedése között. Az adatok és a viselkedés elkülönül, amit a funkcionális programozás támogatja.

Megbeszélés

Foglaljuk össze a kifejezési probléma szabályait:

  • 1. szabály: Engedélyezze a végrehajtását meglévő viselkedések  kell alkalmazni új osztályok
  • 2. szabály:  Engedélyezze a végrehajtását új viselkedések kell alkalmazni meglévő osztályok
  • 3. szabály: Nem veszélyeztetheti a típusú biztonság
  • 4. szabály: Nem szükséges újrafordítani meglévő kódot

Az 1. szabály az altípus polimorfizmussal azonnal megoldható.

Az imént bemutatott TC minta (lásd az előző képernyőképet) megoldja a 2. szabályt. Típusbiztos (3. szabály), és soha nem érintettük meg a `Lib1` (4. szabály). 

Használata azonban több okból nem praktikus:

  • A 33-34. sorokban kifejezetten át kell adnunk a viselkedést a példánya mentén. Ez plusz rezsi. Csak azt kellene írnunk, hogy `useLastBlock(Bitcoin())`.
  • 31. sorban a szintaxis nem gyakori. Szívesebben írnánk egy tömör és objektumorientáltabb  `-tinstance.lastBlock()` kijelentés.

Kiemeljünk néhány Scala funkciót a TC gyakorlati használatához. 

Továbbfejlesztett fejlesztői élmény

A Scala egyedülálló funkciókkal és szintaktikai cukrokkal rendelkezik, amelyek a TC-t igazán élvezetes élménnyé teszik a fejlesztők számára.

Implicit

Az implicit hatókör egy olyan speciális hatókör, amelyet fordítási időben oldanak meg, és egy adott típusból csak egy példány létezhet. 

Egy program egy példányt az implicit hatókörbe helyez a `-valgiven` kulcsszó. Alternatív megoldásként a program lekérhet egy példányt az implicit hatókörből a ` kulcsszóvalusing`.

Az implicit hatókör a fordítási időben feloldásra kerül, és futás közbeni dinamikus megváltoztatására is van mód. Ha a program fordít, az implicit hatókör feloldásra kerül. Futás közben nem lehetséges, hogy hiányzó implicit példányok legyenek használatban. Az egyetlen lehetséges félreértés a rossz implicit példány használatából fakadhat, de ez a probléma a szék és a billentyűzet közötti lénynek marad.

Ez különbözik a globális hatókörtől, mert: 

  1. Kontextus szerint van megoldva. Egy program két helye használhat egy adott típusú példányt implicit hatókörben, de ez a két példány különbözhet.
  2. A színfalak mögött a kód implicit argumentumokat ad át annak működéséhez, amíg el nem éri az implicit használatot. Nem használ globális memóriateret.

Visszatérve a típusórára! Vegyük pontosan ugyanezt a példát.

Scala

def todo = 42
type Height = Int
type Block = Int
def http(uri: String): Block = todo

object Lib1:
 trait Blockchain:
 def getBlock(height: Height): Block

 case class Ethereum() extends Blockchain:
 override def getBlock(height: Height) = todo

 case class Bitcoin() extends Blockchain:
 override def getBlock(height: Height) = todo

`Lib1` ugyanaz a módosítatlan kód, amelyet korábban definiáltunk. 

Scala

object Lib2:
 import Lib1.*

 trait LastBlock[A]:
 def lastBlock(instance: A): Block

 given ethereumLastBlock:LastBlock[Ethereum] = new LastBlock[Ethereum]:
 def lastBlock(eth: Ethereum) = eth.lastBlock

 given bitcoinLastBlock:LastBlock[Bitcoin] = new LastBlock[Bitcoin]:
 def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")

import Lib1.*, Lib2.*

def useLastBlock[A](instance: A)(using behavior: LastBlock[A]) =
 behavior.lastBlock(instance)

println(useLastBlock(Ethereum(lastBlock = 2)))
println(useLastBlock(Bitcoin()))

19. sor egy új viselkedésLastBlock` pontosan úgy van definiálva, mint korábban.

22. sor és 25. sor, `val` helyébe ` lépgiven`. A `LastBlock` az implicit hatókörbe kerülnek.

31. soruseLastBlock` kijelenti a viselkedést `LastBlock` implicit paraméterként. A fordító feloldja a ` megfelelő példányátLastBlock` implicit hatókörből, kontextusba helyezve a hívó helyéről (33. és 34. sor). A 28. sor mindent importál innen: `Lib2', beleértve az implicit hatályt is. Tehát a fordító a példányok 22. és 25. sorát adja át a `useLastBlock`. 

Könyvtárhasználóként a típusosztály használata egyszerűbb, mint korábban. A 34. és 35. sor a fejlesztőnek csak meg kell győződnie arról, hogy a viselkedés egy példánya az implicit hatókörbe van beillesztve (és ez lehet egy egyszerű "import`). Ha egy implicit nem `given` ahol a kód van `using` azt mondja neki a fordító.

A Scala implicit módon megkönnyíti az osztálypéldányok átadását a viselkedésük példányaival együtt.

Implicit cukrok

Az előző kód 22. és 25. sora tovább javítható! Ismételjük meg a TC-megvalósításokat.

Scala

given LastBlock[Ethereum] = new LastBlock[Ethereum]:
 def lastBlock(eth: Ethereum) = eth.lastBlock

 given LastBlock[Bitcoin] = new LastBlock[Bitcoin]:
 def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")

A 22. és 25. sor, ha a példány neve nem használt, akkor elhagyható.

Scala


 given LastBlock[Ethereum] with
 def lastBlock(eth: Ethereum) = eth.lastBlock

 given LastBlock[Bitcoin] with
 def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")

A 22. és 25. sorban a típus ismétlődése `-re cserélhetőwith` kulcsszó.

Scala

given LastBlock[Ethereum] = _.lastBlock

 given LastBlock[Bitcoin] = _ => http("http://bitcoin/last")

Mivel egy degenerált tulajdonságot használunk egyetlen funkcióval, az IDE javasolhatja a kód egyszerűsítését egy SAM kifejezéssel. Bár helyes, nem hiszem, hogy ez a SAM megfelelő használata, hacsak nem véletlenül kódol golfozni.

A Scala szintaktikai cukrokat kínál a szintaxis egyszerűsítésére, eltávolítva a szükségtelen elnevezéseket, deklarációkat és típusredundanciákat.

Kiterjesztés

Okosan használva a `extension` mechanizmus leegyszerűsítheti a szintaxist egy típusosztály használatához.

Scala

object Lib2:
 import Lib1.*

 trait LastBlock[A]:
 def lastBlock(instance: A): Block

 given LastBlock[Ethereum] with
 def lastBlock(eth: Ethereum) = eth.lastBlock

 given LastBlock[Bitcoin] with
 def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")

 extension[A](instance: A)
 def lastBlock(using tc: LastBlock[A]) = tc.lastBlock(instance)

import Lib1.*, Lib2.*

println(Ethereum(lastBlock = 2).lastBlock)
println(Bitcoin().lastBlock)

A 28-29. sorok egy általános kiterjesztési módszer `lastBlock` bármely ` esetén definiálva vanA`egy `-velLastBlock` TC paraméter implicit hatókörben.

A 33-34. sorok a kiterjesztés egy objektumorientált szintaxist használ a TC használatához.

Scala

object Lib2:
 import Lib1.*

 trait LastBlock[A]:
 def lastBlock(instance: A): Block

 given LastBlock[Ethereum] with
 def lastBlock(eth: Ethereum) = eth.lastBlock

 given LastBlock[Bitcoin] with
 def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")

 extension[A](instance: A)(using tc: LastBlock[A])
 def lastBlock = tc.lastBlock(instance)
 def penultimateBlock = tc.lastBlock(instance) - 1

import Lib1.*, Lib2.*

val eth = Ethereum(lastBlock = 2)
println(eth.lastBlock)
println(eth.penultimateBlock)

val btc = Bitcoin()
println(btc.lastBlock)
println(btc.penultimateBlock)

A 28. sorban a TC paraméter a teljes kiterjesztésre is definiálható az ismétlődés elkerülése érdekében. A 30. sorban újra felhasználjuk a TC-t a kiterjesztésben a ` meghatározásáhozpenultimateBlock` (annak ellenére, hogy megvalósítható a `LastBlock`tulajdonság közvetlenül)

A varázslat akkor történik, amikor a TC-t használják. A kifejezés sokkal természetesebbnek tűnik, azt az illúziót keltve, hogy a viselkedés "lastBlock` összekeverik a példánysal.

Általános típus TC-vel
Scala

import Lib1.*, Lib2.*

def useLastBlock1[A](instance: A)(using LastBlock[A]) = instance.lastBlock

def useLastBlock2[A: LastBlock](instance: A) = instance.lastBlock

val eth = Ethereum(lastBlock = 2)
assert(useLastBlock1(eth) == useLastBlock2(eth))

A 34. sor a függvény implicit TC-t használ. Vegye figyelembe, hogy a TC-t nem kell elnevezni, ha ez a név szükségtelen.

A TC-mintát olyan széles körben használják, hogy létezik egy általános típusszintaxis az „implicit viselkedésű típus” kifejezésére. A 36. sor szintaxisa tömörebb alternatívája az előzőnek (34. sor). Ez elkerüli a névtelen implicit TC paraméter konkrét deklarálását.

Ezzel a fejlesztői tapasztalatokkal foglalkozó rész véget ért. Láttuk, hogy a kiterjesztések, az implicit elemek és néhány szintaktikai cukor kevésbé zsúfolt szintaxist biztosíthatnak a TC használatakor és definiálásakor.

Automatikus levezetés

Sok Scala könyvtár használja a TC-t, így a programozóra bízza, hogy implementálja azokat a kódbázisába.

Például a Circe (egy json de-szerializációs könyvtár) a TC `-t használjaEncoder[T]` és `Decoder[T]` a programozóknak, hogy implementálják a kódbázisukban. A megvalósítás után a könyvtár teljes hatóköre használható. 

A TC ezen megvalósításai több mint gyakran előfordulnak adatorientált térképezők. Nincs szükségük semmilyen üzleti logikára, unalmasak az írásuk, és teher az esetosztályokkal való szinkronban tartásuk.

Ilyen helyzetben azok a könyvtárak kínálják az ún automatikus levezetés ill félautomata származtatás. Lásd például Circe automatikus és a félautomata származtatás. A félautomata származtatással a programozó egy típusosztály példányát kisebb szintaxissal deklarálhatja, míg az automatikus származtatáshoz nem szükséges semmilyen kódmódosítás, kivéve az importálást.

A motorháztető alatt, fordítási időben, általános makrók önvizsgálatot végeznek típusok tiszta adatstruktúraként, és létrehoz egy TC[T]-t a könyvtárhasználók számára. 

A TC általános származtatása nagyon elterjedt, ezért a Scala egy teljes eszköztárat vezetett be erre a célra. Ezt a módszert nem mindig hirdetik a könyvtári dokumentációk, bár ez a Scala 3 módszere a származtatás használatára.

Scala

object GenericLib:

 trait Named[A]:
 def blockchainName(instance: A): String

 object Named:
 import scala.deriving.*

 inline final def derived[A](using inline m: Mirror.Of[A]): Named[A] =
 val nameOfType: String = inline m match
 case p: Mirror.ProductOf[A] => compiletime.constValue[p.MirroredLabel]
 case _ => compiletime.error("Not a product")
 new Named[A]:
 override def blockchainName(instance: A):String = nameOfType.toLowerCase

 extension[A] (instance: A)(using tc: Named[A])
 def blockchainName = tc.blockchainName(instance)

import Lib1.*, GenericLib.*

case class Polkadot() derives Named
given Named[Bitcoin] = Named.derived
given Named[Ethereum] = Named.derived

println(Ethereum(lastBlock = 2).blockchainName)
println(Bitcoin().blockchainName)
println(Polkadot().blockchainName)

18. sor egy új TC `Named` kerül bemutatásra. Ez a TC szigorúan véve nem kapcsolódik a blokklánc üzlethez. Célja a blokklánc elnevezése az esetosztály neve alapján.

Először összpontosítson a definíciók 36-38. soraira. 2 szintaxis létezik a TC származtatására:

  1. A 36. sor a TC-példányt közvetlenül az esetosztályban definiálhatja a `-valderives` kulcsszó. A burkolat alatt a fordító egy adott `-t generálNamed` példány a `-benPolkadot` társobjektum.
  2. 37. és 38. sor, a típusosztályok példányai a már létező osztályokon vannak megadva `TC.derived

A 31. sor egy általános kiterjesztést definiál (lásd az előző szakaszokat) és a `blockchainName` természetesen használatos.  

A `derives` kulcsszó egy metódust vár ` formávalinline def derived[T](using Mirror.Of[T]): TC[T] = ???` ami a 24. sor definiált. Nem fogom részletesen elmagyarázni, mit csinál a kód. Nagy vonalakban:

  • `inline def` makrót határoz meg
  • `MirrorA ` a típusok betekintését lehetővé tevő eszköztár része. Különféle tükrök léteznek, és a kód 26. sora a `-re összpontosítProduct` tükrök (a tokosztály egy termék). 27. sor, ha a programozók olyasmit próbálnak származtatni, ami nem `Product`, a kód nem fordítódik le.
  • a `Mirror` más típusokat is tartalmaz. Egyikük, `MirrorLabel`, egy karakterlánc, amely tartalmazza a típus nevét. Ezt az értéket a ` implementációjának 29. sora használjaNamed` TC.

A TC szerzők metaprogramozást használhatnak olyan függvények biztosítására, amelyek általánosan generálnak TC-példányokat adott típushoz. A programozók dedikált könyvtári API-t vagy a Scala származékos eszközöket használhatják kódjuk példányainak létrehozásához.

Akár általános, akár specifikus kódra van szüksége a TC megvalósításához, minden helyzetre van megoldás. 

Összefoglaló az összes előnyről

  • Megoldja a kifejezési problémát
    • Az új típusok a hagyományos tulajdonságok öröklődésén keresztül valósíthatják meg a meglévő viselkedést
    • Új viselkedések implementálhatók a meglévő típusokon
  • Az aggodalom szétválasztása
    • A kód nincs összevissza és könnyen törölhető. A TC elválasztja az adatokat és a viselkedést, ami a funkcionális programozás mottója.
  • Biztonságos
    • Típusbiztos, mert nem az önvizsgálatra támaszkodik. Ez elkerüli a típusokat érintő nagy mintaillesztést. Ha találkozik ilyen kód írásával, olyan esetet észlelhet, amikor a TC minta tökéletesen megfelel.
    • Az implicit mechanizmus biztonságosan lefordítható! Ha egy példány hiányzik a fordításkor, a kód nem fordítódik le. Nincs meglepetés futás közben.
  • Ad-hoc polimorfizmust hoz
    • Az ad hoc polimorfizmus általában hiányzik a hagyományos objektum orientált programozásból.
    • Az ad-hoc polimorfizmus révén a fejlesztők ugyanazt a viselkedést valósíthatják meg különböző nem kapcsolódó típusoknál, anélkül, hogy hagyományos altípust használnának (ami párosítja a kódot).
  • A függőségi injekció egyszerűvé vált
    • A TC-példány a Liskov-helyettesítés elvének megfelelően módosítható. 
    • Ha egy komponens függ egy TC-től, egy megcsúfolt TC könnyen befecskendezhető tesztelési célból. 

Ellenjelzések

Minden kalapács számos problémára készült.

A típusosztályok viselkedési problémákra szolgálnak, és nem használhatók adatöröklésre. Erre a célra használjon kompozíciót.

A szokásos altípusozás egyszerűbb. Ha Ön a kódbázis tulajdonosa, és nem törekszik a bővíthetőségre, a típusosztályok túlzásba eshetnek.

Például a Scala magban van egy `Numeric` típusú osztály:

Scala

trait Numeric[T] extends Ordering[T] {
 def plus(x: T, y: T): T
 def minus(x: T, y: T): T
 def times(x: T, y: T): T

Valóban ésszerű ilyen típusú osztályt használni, mert nem csak az algebrai algoritmusok újrafelhasználását teszi lehetővé a Scalába beágyazott típusokon (Int, BigInt, …), hanem a felhasználó által definiált típusokon is (a `ComplexNumber` például).

Másrészt a Scala gyűjtemények megvalósítása többnyire altípust használ a típusosztály helyett. Ennek a kialakításnak több okból is van értelme:

  • A gyűjtemény API-nak teljesnek és stabilnak kell lennie. Felfedi a gyakori viselkedést a megvalósítások által örökölt tulajdonságokon keresztül. A nagymértékben bővíthetőség itt nem különösebben cél.
  • Egyszerűen használhatónak kell lennie. A TC mentális többletköltséget jelent a végfelhasználói programozó számára.
  • A TC kisebb teljesítményköltséget is jelenthet. Ez kritikus lehet egy gyűjtemény API-nál.
  • A gyűjtemény API azonban továbbra is bővíthető a harmadik fél könyvtárai által meghatározott új TC segítségével.

Következtetés

Láttuk, hogy a TC egy egyszerű minta, amely megold egy nagy problémát. A Scala gazdag szintaxisának köszönhetően a TC minta sokféleképpen megvalósítható és használható. A TC minta összhangban van a funkcionális programozási paradigmával, és mesés eszköz a tiszta architektúrához. Nincs ezüst golyó, és TC mintát kell alkalmazni, amikor illeszkedik.

Reméljük, hogy ennek a dokumentumnak a elolvasása után ismereteket szerzett. 

A kód elérhető a címen https://github.com/jprudent/type-class-article. Ha bármilyen kérdése vagy észrevétele van, forduljon hozzám. Ha akarja, használhat problémákat vagy kód megjegyzéseket a tárolóban.


Jerome PRUDENT

Software Engineer

Időbélyeg:

Még több Főkönyv