Néhány hónapja kezdtem dolgozni a Scalával. Az egyik olyan fogalom, amelynek megértése a legnehezebb volt, a Either
monád. Tehát úgy döntöttem, hogy játszom vele, és jobban megértem az erejét.
Ebben a történetben megosztom a tanultakat, remélve, hogy segítek a kódolóknak megközelíteni ezt a gyönyörű nyelvet.
Bármelyik monád
Either
a Scala egyik leghasznosabb monádja. Ha kíváncsi vagy arra, mi a monád, nos ... itt nem részletezhetem a részleteket, talán egy későbbi történetben!
Képzelje el, Either
mint egy számítást tartalmazó dobozt. Addig dolgozol ebben a mezőben, amíg el nem dönted az eredmény elérését.
Ebben a konkrét esetben Either
dobozunknak két „formája” lehet. Ez lehet (akár) a Left
vagy a Right
, a benne lévő számítás eredményétől függően.
Hallom, amikor azt kérdezi: "OK, és mire hasznos?"
A szokásos válasz: hibakezelés.
Helyezhetünk egy számítást a hibába, Either
és hibává tehetjük azt Left
, vagy Right
siker esetén eredményt tartalmazhatunk. A Left
hibák és Right
a siker használata konvenció. Értsük meg ezt valamilyen kóddal!
Ebben a részletben csak egy Either
változót határozunk meg .
Meghatározhatjuk Right
érvényes értéket Left
tartalmazóként vagy hibát tartalmazóként. Van olyan számításunk is, amely egy an-t ad vissza Either
, vagyis lehet a Left
vagy a Right
. Egyszerű, nem igaz?
Jobb és bal vetület
Miután a számítás a mezőbe kerül, érdemes kihozni belőle az értéket. Biztos vagyok benne, számíthat arra, hogy hívja a .get
szóló Either
és kivonat az eredmény.
Ez nem olyan egyszerű.
Gondoljon csak bele: beletette a számítását a Either
, de nem tudja, hogy a Left
vagy a eredményt adott- e Right
. Tehát mit kell .get
visszatérnie egy hívásnak? A hiba, vagy az érték?
Ezért az eredmény eléréséhez feltételeznie kell a számítás eredményét.
Itt jön létre a vetítés .
An-tól kezdve Either
kaphat egy RightProjection
vagy egy LeftProjection
. Az előbbi azt jelenti, hogy feltételezed, hogy a számítás a Right
, az utóbbi a Left
.
Tudom, tudom ... ez kissé zavaró lehet. Jobb valamilyen kóddal megérteni. Végül is a kód mindig igazat mond .
Ez az. Ne feledje, hogy amikor megpróbálja megszerezni az eredményt a-ból RightProjection
, de ez egy Left
, akkor kivételt kap. Ugyanez vonatkozik a LeftProjection
és van egy Right
.
A klassz dolog az, hogy leképezheti a vetületeket. Ez azt jelenti, hogy azt mondhatja: „tegyük fel, hogy ez Jobb: tedd meg vele”, Left
változatlanul hagyva (és fordítva).
Az Opciótól bármelyikig
Option
az érvénytelen értékek kezelésének másik gyakori módja.
Egy Option
értéknek lehet értéke, vagy üres is lehet (értéke van Nothing
). Fogadok, hogy észrevett egy hasonlóságot Either
… Még jobb, mert valójában átalakíthatunk egy Option
an-t Either
! Kódidő!
Átalakítható az Option
a-ra Left
vagy a-ra Right
. A Either
végrendelet eredő oldala tartalmazza az értéket, Option
ha meg van adva. Menő. Várjon egy percet ... Mi van, ha az Option
üres? Megkapjuk a másik oldalt, de meg kell határoznunk, hogy mit várunk benne találni.
Kifordítva
Either
varázslat, ebben mindannyian egyetértünk. Tehát úgy döntünk, hogy bizonytalan számításokhoz használjuk. A funkcionális programozás tipikus forgatókönyve a függvény leképezése egy List
elemre vagy egy elemre Map
. Csináljuk a friss, új teljesítményű Either
számításunkkal ...
Huston, van egy "problémánk" (ok, ez nem NAGY probléma, de kissé kényelmetlen). Jobb lenne, ha a gyűjtemény belül lenne, Either
mint a sok Either
benne. Dolgozhatunk ezen.
Lista
Kezdjük azzal List
. Először okoskodunk erről, aztán játszhatunk kóddal.
Ki kell szednünk az értéket a -ból Either
, be kell tennünk a-ba List
, és beletesszük a listát egy Either
. Jó, ez tetszik.
A lényeg az, hogy lehet egy Left
vagy egy Right
, tehát mindkét esetet kezelnünk kell. Amíg nem találunk a-t Right
, addig egy újba tudjuk tenni az értékét List
. Így haladunk, és minden értéket felhalmozunk az újban List
.
Végül elérjük a végén a List
az Either
, ami azt jelenti, hogy van egy új List
, amely az összes értéket. Csomagolhatjuk a-ba Right
és kész. Ez volt az eset, amikor a számításunk nem adott vissza egy Error
a Left
.
Ha ez megtörténik, ez azt jelenti, hogy valami rosszul történt a számítás során, így visszaadhatjuk Left
a Error
. Megvan a logika, most szükségünk van a kódra.
Térkép
A munka Map
meglehetősen egyszerű, miután elkészítettük a házi feladatot List
(annak ellenére, hogy általánosvá kellett tenni):
- Első lépés: átalakítani az
Map
egyList
azEither
, amely tartalmazza a tuple (kulcs, érték). - Második lépés: adja át az eredményt az általunk definiált függvénynek
List
. - Harmadik lépés: átalakítja a
List
a sorok belsejébenEither
egyMap
.
Könnyű Peasy.
Nézzük klassz: hasznos implicit átalakító
Bevezettük Either
és megértettük, hogy hasznos a hibakezelésben. Vetítésekkel játszottunk egy kicsit. Láttuk, hogyan lehet átadni egy Option
olyan Either
. Azt is végrehajtott néhány hasznos funkciók „kivonat” Either
-tól List
és Map
. Eddig jó.
Szeretném befejezni utunkat a Either
monádban, egy kicsit tovább haladva. Az általunk meghatározott segédfunkciók elvégzik a dolgukat, de úgy érzem, valami hiányzik ...
Csodálatos lenne, ha a konverziót közvetlenül a gyűjteményen végeznénk. Nálunk lenne valami myList.toEitherList
vagy myMap.toEitherMap
. Többé-kevésbé tetszik, amit csinálunk a Option.toRight
vagy Option.toLeft
.
Jó hír: implicit osztályok segítségével megtehetjük !
A Scala implicit osztályainak használata kibővíti egy másik osztály képességeit.
In our case, we extend the capability of List
and Map
to automagically “extract” the Either
. The implementation of the conversion is the same we defined before. The only difference is that now we make it generic. Isn’t Scala awesome?
Since this can be a useful utility class, I prepared for you a gist you can copy and paste with ease.
object EitherConverter { implicit class EitherList[E, A](le: List[Either[E, A]]){ def toEitherList: Either[E, List[A]] = { def helper(list: List[Either[E, A]], acc: List[A]): Either[E, List[A]] = list match { case Nil => Right(acc) case x::xs => x match { case Left(e) => Left(e) case Right(v) => helper(xs, acc :+ v) } } helper(le, Nil) } } implicit class EitherMap[K, V, E](me: Map[K, Either[E, V]]) { def toEitherMap: Either[E, Map[K, V]] = me.map{ case (k, Right(v)) => Right(k, v) case (_, e) => e }.toList.toEitherList.map(l => l.asInstanceOf[List[(K, V)]].toMap) } }
Conclusion
That’s all folks. I hope this short story may help you to better understand the Either
monad.
Please note that my implementation is quite simple. I bet there are more complex and elegant ways to do the same thing. I’m a newbie in Scala and I like to KISS, so I prefer readability over (elegant) complexity.
If you have a better solution, especially for the utility class, I will be happy to see it and learn something new! :-)