Cats Effect ist zu einer Art „Reactive Streams“ für die funktionale Scala-Welt geworden, mit der Sie das gesamte vielfältige Ökosystem von Bibliotheken miteinander kombinieren können.
Viele ausgezeichnete Bibliotheken: http4s, fs2, doobie - werden nur auf der Basis von Typklassen von Cats Effect implementiert. Bibliotheken wie ZIO und Monix bieten wiederum Instanzen dieser Typklassen für ihre Effekttypen. Trotz einiger Probleme, die in Version 3.0 behoben werden, hilft Cats Effect vielen Open Source-Mitarbeitern, das gesamte funktionale Ökosystem der Scala-Sprache organisch zu unterstützen. Entwickler, die Cats Effect verwenden, stehen vor einer schwierigen Wahl: Welche Implementierung von Effekten für ihre Anwendungen verwendet werden soll?
Heute gibt es drei Alternativen:
- Cats IO, Referenzimplementierung;
- Monix, der Task-Datentyp und seine Reaktivität im Code;
- ZIO, der ZIO-Datentyp und sein Cross-Threading-Bereich.
In diesem Beitrag werde ich versuchen, Ihnen zu beweisen, dass ZIO für die Erstellung Ihrer Anwendung mit Cats Effect eine gute Wahl ist, da Designlösungen und Funktionen sich stark von der Referenzimplementierung in Cats IO unterscheiden.
1. Bessere MTL / Tagless-Final-Architektur
MTL (Monad Transformers Library) ist ein Programmierstil, bei dem Funktionen nach Art ihres Effekts polymorph sind und ihre Anforderungen durch eine „Typklassenbeschränkung“ ausdrücken. In Scala wird dies oft als tagless-final-Stil bezeichnet (obwohl es nicht dasselbe ist), insbesondere wenn die Typklasse keine Gesetze hat.
Es ist bekannt, dass es unmöglich ist, eine globale Instanz für klassische MTL-Typklassen wie Writer und State sowie für Effekttypen wie Cats IO zu definieren. Das Problem besteht darin, dass Instanzen dieser Typklassen für diese Arten von Effekten Zugriff auf einen veränderlichen Status erfordern, der nicht global erstellt werden kann, da das Erstellen eines veränderlichen Status ebenfalls ein Effekt ist.
Für eine optimale Leistung ist es jedoch wichtig, "Monadentransformatoren" zu vermeiden und die Write and State-Implementierung direkt über den Haupteffekttyp bereitzustellen.
Um dies zu erreichen, verwenden Scala-Programmierer einen Trick: Sie erstellen (aber bereinigen) Instanzen auf der obersten Ebene ihrer Programme mit Effekten und stellen sie dann als lokale Implikationen weiter im Programm bereit:
Ref.make[AppState](initialAppState).flatMap(ref => implicit val monadState = new MonadState[Task, AppState] { def get: Task[AppState] = ref.get def set(s: AppState): Task[Unit] = ref.set(s).unit } myProgram )
Trotz der Tatsache, dass ein solcher Trick nützlich ist, ist er immer noch eine „Krücke“. In einer idealen Welt könnten alle Instanzen von Typklassen kohärent sein (eine Instanz pro Typ) und nicht lokal erstellt werden, wodurch Effekte erzeugt werden, und sich dann auf magische Weise in implizite Werte einwickeln, die von nachfolgenden Methoden verwendet werden.
Eine großartige Funktion von MTL / tagless-final ist, dass Sie die meisten Instanzen direkt über dem ZIO-Datentyp mithilfe der ZIO-Umgebung definieren können.
Hier ist eine Möglichkeit, eine globale MonadState-Definition für einen ZIO-Datentyp zu erstellen:
trait State[S] { def state: Ref[S] } implicit def ZIOMonadState[S, R <: State[S], E]: MonadState[ZIO[R, E, ?], S] = new MonadState[ZIO[R, E, ?], S] { def get: ZIO[R, E, S] = ZIO.accessM(_.state.get) def set(s: S): ZIO[R, E, Unit] = ZIO.accessM(_.state.set(s).unit) }
Eine Instanz wird jetzt global für jede Umgebung definiert, die mindestens
State[S]
.
Ähnliches gilt für
FunctorListen
, auch bekannt als
MonadWriter
:
trait Writer[W] { def writer: Ref[W] } implicit def ZIOFunctorListen[W: Semigroup, R <: Writer[W], E]: FunctorListen[ZIO[R, E, ?], W] = new FunctorListen[ZIO[R, E, ?], W] { def listen[A](fa: ZIO[R, E, A]): ZIO[R, E, (A, W)] = ZIO.accessM(_.state.get.flatMap(w => fa.map(a => a -> w))) def tell(w: W): ZIO[R, E, W] = ZIO.accessM(_.state.update(_ |+| w).unit) }
Und natürlich können wir dasselbe für
MonadError
:
implicit def ZIOMonadError[R, E]: MonadError[ZIO[R, E, ?], E] = new MonadError[ZIO[R, E, ?], E]{ def handleErrorWith[A](fa: ZIO[R, E, A])(f: E => ZIO[R, E, A]): ZIO[R, E, A] = fa catchAll f def raiseError[A](e: E): ZIO[R, E, A] = ZIO.fail(e) }
Diese Technik ist leicht auf andere Typklassen anwendbar, einschließlich Tagless-Final-Typklassen, für deren Instanzen möglicherweise Effekte (Änderungen, Konfigurationen) generiert werden müssen, Testfunktionen, die Effekte generieren (Kombination von Umgebungseffekten mit Tagless-Final), oder alles andere, auf das über die Umgebung leicht zugegriffen werden kann .
Keine langsamen monadischen Transformationen mehr! Sagen wir "Nein" zum Erstellen von Effekten beim Initialisieren von Instanzen der Klassenklasse und zu lokalen Auswirkungen. Keine Krücken mehr nötig. Direktes Eintauchen in die reine Funktionsprogrammierung.
2. Ressourcen für bloße Sterbliche sparen
Eine der ersten Funktionen von ZIO war die Interraption - die Fähigkeit der ZIO-Laufzeit, ausführbare Effekte sofort zu unterbrechen und garantiert alle Ressourcen freizugeben. Eine grobe Implementierung dieser Funktion traf Cats IO.
Haskell nannte diese Funktionalität eine asynchrone Ausnahme, mit der Sie Latenzzeiten, effiziente parallele und wettbewerbsfähige Vorgänge und global optimale Berechnungen erstellen und effizient nutzen können. Solche Unterbrechungen bringen nicht nur große Vorteile mit sich, sondern stellen auch komplexe Aufgaben im Bereich der Unterstützung des sicheren Zugangs zu Ressourcen.
Programmierer sind es gewohnt, Fehler in Programmen durch einfache Analyse zu verfolgen. Dies kann auch mit ZIO erfolgen, das ein Typsystem verwendet, um Fehler zu erkennen. Aber Unterbrechung ist etwas anderes. Ein aus vielen anderen Effekten erzeugter Effekt kann an jeder Grenze unterbrochen werden.
Betrachten Sie den folgenden Effekt:
for { handle <- openFile(file) data <- readFile(handle) _ <- closeFile(handle) } yield data
Die meisten Entwickler werden von diesem Szenario nicht überrascht sein:
closeFile
wird nicht ausgeführt, wenn
readFile
abstürzt. Glücklicherweise verfügt das Effektsystem über eine
ensuring
(
guarantee
in Cats Effect), mit der Sie dem Finalizer-Effekt einen Final-Handler hinzufügen können, ähnlich wie bei finally.
Das Hauptproblem des obigen Codes kann also leicht gelöst werden:
for { handle <- openFile(file) data <- readFile(handle).ensuring(closeFile(handle)) } yield ()
Jetzt ist der Effekt "
readFile
" geworden, in dem Sinne, dass die Datei immer noch geschlossen bleibt, wenn die
readFile
kaputt geht. Wenn
readFile
erfolgreich ist, wird die Datei ebenfalls geschlossen. In allen Fällen wird die Datei geschlossen.
Aber immer noch gar nicht ganz. Unterbrechung bedeutet, dass der Effekt überall unterbrochen werden kann, auch zwischen
openFile
und
readFile
. In diesem Fall wird die geöffnete Datei nicht geschlossen und es tritt ein Ressourcenleck auf.
Das Muster zum Abrufen und Freigeben einer Ressource ist so weit verbreitet, dass ZIO einen Bracket-Operator einführte, der auch in Cats Effect 1.0 enthalten war. Die Bracket-Anweisung schützt vor Unterbrechungen: Wenn die Ressource erfolgreich empfangen wurde, erfolgt die Freigabe auch dann, wenn der Effekt, der die Ressource verwendet, unterbrochen wird. Ferner kann weder der Empfang noch die Freigabe der Ressource unterbrochen werden, wodurch eine Garantie für die Ressourcensicherheit gegeben ist.
Mit der Klammer würde das obige Beispiel folgendermaßen aussehen:
openFile(file).bracket(closeFile(_))(readFile(_))
Leider kapselt die Klammer nur ein (ziemlich allgemeines) Ressourcenverbrauchsmuster. Es gibt viele andere, insbesondere bei wettbewerbsfähigen Datenstrukturen, auf die für Interrupts zugegriffen werden muss, da sonst Lecks möglich sind.
Im Allgemeinen sind alle Interrupt-Arbeiten auf zwei Hauptpunkte zurückzuführen:
- Vermeiden Sie Unterbrechungen in einigen Bereichen, die möglicherweise unterbrochen werden.
- Unterbrechung in Bereichen zulassen, die einfrieren können.
ZIO kann beides implementieren. Zum Beispiel können wir unsere eigene Version von Bracket mithilfe von ZIO-Abstraktionen auf niedriger Ebene entwickeln:
ZIO.uninterruptible { for { a <- acquire exit <- ZIO.interruptible(use(a)) .run.flatMap(exit => release(a, exit) .const(exit)) b <- ZIO.done(exit) } yield b }
In diesem Code ist die
use(a)
der einzige Teil, der unterbrochen werden kann. Der umgebende Code garantiert in jedem Fall die Ausführung der
release
.
Sie können jederzeit prüfen, ob die Möglichkeit von Unterbrechungen besteht. Hierzu werden nur zwei primitive Operationen benötigt (der Rest wird von ihnen abgeleitet).
Mit diesem kompositorischen Interrupt-Modell mit vollem Funktionsumfang können Sie nicht nur eine einfache Bracket-Implementierung implementieren, sondern auch andere Szenarien im Ressourcenmanagement implementieren, in denen ein Gleichgewicht zwischen den Vor- und Nachteilen von Interrupts gefunden wird.
Cats IO bietet nur eine Operation zur Steuerung von Interrupts: den nicht abbrechbaren Kombinator. Dadurch wird der gesamte Codeblock nicht unterbrochen. Obwohl dieser Vorgang selten verwendet wird, kann er zu einem Ressourcenleck oder zu Sperren führen.
Gleichzeitig stellt sich heraus, dass Sie in Cats IO ein Grundelement definieren können, mit dem Sie mehr Kontrolle über Interrupts erhalten. Die sehr komplizierte Implementierung von Fabio Labella erwies sich als äußerst langsam.
Mit ZIO können Sie Code mit Unterbrechungen schreiben, der mit deklarativen zusammengesetzten Anweisungen auf hoher Ebene arbeitet, und Sie müssen nicht zwischen schwerer Komplexität in Kombination mit geringer Leistung und blockierenden Lecks wählen.
Darüber hinaus ermöglicht der kürzlich hinzugefügte Software-Transaktionsspeicher in ZIO dem Benutzer das deklarative Schreiben von Datenstrukturen und Code, die automatisch asynchron und wettbewerbsfähig sind und Interrupts zulassen.
3. Garantierte Finalizer
Der try / finally-Block in vielen Programmiersprachen bietet die erforderlichen Garantien, um synchronen Code zu schreiben, ohne Ressourcen zu verlieren.
Dieser Block garantiert insbesondere Folgendes: Wenn ein try-Block die Ausführung startet, wird der finally-Block ausgeführt, wenn try versucht wird.
Diese Garantie gilt für:
- Es gibt verschachtelte "try / finally" -Blöcke.
- Es gibt Fehler im "Try-Block".
- Es gibt Fehler im verschachtelten finally-Block.
Der ZIO-Sicherstellungsvorgang kann genau wie try / finally verwendet werden:
val effect2 = effect.ensuring(cleanup)
ZIO bietet die folgenden Garantien für "effect.ensuring (finalizer)": Wenn "effect" ausgeführt wurde, startet "finalizer" die Ausführung, wenn "effect" stoppt.
Wie bei try / finally bleiben diese Garantien in den folgenden Fällen bestehen:
- Es gibt verschachtelte "Sicherstellungs" -Kompositionen;
- es gibt Fehler in der "Wirkung";
- Es gibt Fehler im verschachtelten "Finalizer".
Darüber hinaus bleibt die Garantie auch dann erhalten, wenn der Effekt unterbrochen wird (Garantien für die „Klammer“ sind ähnlich, tatsächlich wird „Klammer“ für „Sicherstellung“ implementiert).
Der Datentyp Cats IO bietet eine weitere, schwächere Garantie. Für "effect.guarantee (finalizer)" wird es wie folgt geschwächt: Wenn "effect" ausgeführt wurde, startet "finalizer" die Ausführung, wenn "effect" stoppt, wenn der Problemeffekt nicht in "effect" eingefügt wird.
Eine schwächere Garantie findet sich auch bei der Implementierung der „Klammer“ in Cats IO.
Um ein Ressourcenleck zu erhalten, verwenden Sie einfach den Effekt, der im Effekt "Garantie" oder "bracket.use" verwendet wird, und setzen Sie ihn wie folgt zusammen:
Wenn bigTrouble auf diese Weise in einen anderen Effekt eingefügt wird, wird der Effekt nicht unterbrochen - es werden keine durch die "Garantie" festgelegten "Finalizer" oder die Reinigung von Ressourcen durch die "Klammer" nicht ausgeführt. All dies führt zu einem Ressourcenverbrauch, selbst wenn sich ein „Finalizer“ im Block befindet.
Beispielsweise startet der "Finalizer" im folgenden Code niemals die Ausführung:
(IO.unit >> bigTrouble).guarantee(IO(println("Won't be executed!!!«)))
Bei der Auswertung des Codes ohne Berücksichtigung des globalen Kontexts kann nicht festgestellt werden, ob ein Effekt wie "bigTrouble" an einer beliebigen Stelle im "use" -Effekt der Operation "bracket" oder im Block "finalizer" eingefügt wird.
Daher können Sie nicht herausfinden, ob das Cats IO-Programm mit Ressourcenlecks oder fehlenden "Finalizer" -Blöcken funktioniert, ohne das gesamte Programm zu bewerten. Das gesamte Programm kann nur manuell ausgewertet werden, und dieser Vorgang geht immer mit Fehlern einher, die vom Compiler nicht überprüft werden können. Darüber hinaus muss dieser Vorgang jedes Mal wiederholt werden, wenn wichtige Änderungen im Code auftreten.
ZIO hat eine benutzerdefinierte Implementierung von "Garantie" von Cats Effect, "Garantiefall" und "Klammer". Implementierungen verwenden native ZIO-Semantik (nicht Cats IO-Semantik), mit der wir mögliche Probleme mit Ressourcenlecks hier und jetzt bewerten können, da wir wissen, dass in allen Situationen Finalizer gestartet und Ressourcen freigegeben werden.
4. Stabiles Schalten
Cats Effect verfügt über die Methode "evalOn" von "ContextShift", mit der Sie die Ausführung eines Codes in einen anderen Ausführungskontext verschieben können.
Dies ist aus mehreren Gründen nützlich:
- Viele Client-Bibliotheken zwingen Sie dazu, einige Arbeiten in ihrem Thread-Pool auszuführen.
- UI-Bibliotheken erfordern einige Aktualisierungen im UI-Thread.
- Einige Effekte erfordern die Isolierung von Thread-Pools, die an ihre spezifischen Funktionen angepasst sind.
Die Operation „EvalOn“ führt den Effekt dort aus, wo er ausgeführt werden soll, und kehrt dann zum ursprünglichen Ausführungskontext zurück. Zum Beispiel:
cs.evalOn(kafkaContext)(kafkaEffect)
Hinweis: Cats IO verfügt über ein ähnliches "Shift" -Konstrukt, mit dem Sie in einen anderen Kontext wechseln können, ohne zurück zu gehen. In der Praxis ist dieses Verhalten jedoch selten erforderlich. Daher wird "evalOn" bevorzugt.
Die ZIO-Implementierung von "evalOn" (erstellt auf dem ZIO-Grundelement "lock") bietet die Garantien, die erforderlich sind, um eindeutig zu verstehen, wo der Effekt funktioniert - der Effekt wird immer in einem bestimmten Kontext ausgeführt.
Cats IO hat eine andere, schwächere Garantie - der Effekt wird in einem bestimmten Kontext bis zur ersten asynchronen Operation oder internen Umschaltung ausgeführt.
In Anbetracht eines kleinen Codeteils ist es unmöglich, sicher zu wissen, ob ein asynchroner Effekt (oder eine verschachtelte Umschaltung) in den umzuschaltenden Effekt integriert wird, da Asynchronität in Typen nicht angezeigt wird.
Wie im Fall der Ressourcensicherheit ist es daher erforderlich, das gesamte Programm zu untersuchen, um zu verstehen, wo der Cats IO-Effekt ausgelöst wird. In der Praxis und aus meiner Erfahrung sind Cats IO-Benutzer überrascht, wenn bei der Verwendung von „evalOn“ in einem Kontext später festgestellt wird, dass der größte Teil des Effekts versehentlich in einem anderen ausgeführt wurde.
Mit ZIO können Sie festlegen, wo Effekte ausgelöst werden sollen, und darauf vertrauen, dass dies in allen Fällen der Fall ist, unabhängig davon, wie die Effekte in andere Effekte integriert sind.
5. Sicherheit von Fehlermeldungen
Jeder Effekt, der Parallelität, Parallelität oder sicheren Zugriff auf Ressourcen unterstützt, führt zu einem linearen Fehlermodell: Im Allgemeinen können nicht alle Fehler gespeichert werden.
Dies gilt sowohl für "Throwable", einen in Cats IO integrierten festen Fehlertyp, als auch für den von ZIO unterstützten polymorphen Fehlertyp.
Beispiele für Situationen mit mehreren einmaligen Fehlern:
- Finalizer löst eine Ausnahme aus.
- zwei (fallende) Effekte werden parallel ausgeführt;
- zwei (fallende) Effekte im Rennzustand;
- Der unterbrochene Effekt lässt nach, bevor der Abschnitt vor Unterbrechungen geschützt bleibt.
Da nicht alle Fehler gespeichert werden, bietet ZIO eine "Cause [E]" - Datenstruktur basierend auf einem freien Semiring (eine Abstraktion von der abstrakten Algebra, deren Wissen hier nicht angenommen wird), die es ermöglicht, serielle und parallele Fehler für jede Art von Fehler zu verbinden. Während aller Vorgänge (einschließlich der Reinigung auf einen heruntergefallenen oder unterbrochenen Effekt) aggregiert ZIO Fehler in der Datenstruktur „Ursache [E]“. Diese Datenstruktur ist jederzeit verfügbar. Infolgedessen speichert ZIO immer alle Fehler: Sie sind immer verfügbar, können protokolliert, untersucht und transformiert werden, wie es die Geschäftsanforderungen erfordern.
Cats IO hat ein Modell mit Verlust von Fehlerinformationen ausgewählt. Während ZIO die beiden Fehler über Ursache [E] verbindet, verliert Cats IO eine der Fehlermeldungen, indem es beispielsweise "e.printStackTrace ()" für den auftretenden Fehler aufruft.
Beispielsweise geht ein Fehler im „Finalizer“ in diesem Code verloren.
IO.raiseError(new Error("Error 1")).guarantee(IO.raiseError(new Error("Error 2«)))
Dieser Ansatz zur Verfolgung von Fehlern bedeutet, dass Sie nicht das gesamte Spektrum der Fehler lokal lokalisieren und verarbeiten können, die aufgrund der Kombination von Effekten auftreten. Mit ZIO können Sie jede Art von Fehler verwenden, einschließlich "Throwable" (oder spezifischerer Untertypen wie "IOExceptio" oder einer anderen benutzerdefinierten Ausnahmehierarchie), um sicherzustellen, dass während der Programmausführung keine Fehler verloren gehen.
6. Asynchronität ohne Deadlocks
Sowohl ZIO als auch Cats IO bieten einen Konstruktor, mit dem Sie Code mit einem Rückruf nehmen und in Kraft setzen können
Diese Funktion wird über die Async-Pipe-Klasse in Cats Effect bereitgestellt:
val effect: Task[Data] = Async[Task].async(k => getDataWithCallbacks( onSuccess = v => k(Right(v)), onFailure = e => k(Left(e)) ))
Dadurch wird ein asynchroner Effekt erstellt, der bei Ausführung wartet, bis der Wert angezeigt wird, und dann fortgesetzt wird. All dies ist für den Benutzer des Effekts offensichtlich. Daher ist die funktionale Programmierung für die Entwicklung von asynchronem Code so attraktiv.
Beachten Sie, dass die Rückruffunktion (hier heißt sie "k") aufgerufen wird, sobald sich der Rückrufcode in einen Effekt verwandelt. Diese Rückruffunktion wird mit einem Erfolgs- / Fehlerwert beendet. Wenn diese Rückruffunktion aufgerufen wird, wird die Ausführung des Effekts (zuvor angehalten) fortgesetzt.
ZIO garantiert, dass der Effekt die Ausführung im Laufzeit-Thread-Pool wieder aufnimmt, wenn der Effekt keinem bestimmten speziellen Kontext oder einem anderen Kontext zugewiesen wurde, an den der Effekt angehängt wurde.
Cats IO setzt den Effekt auf den Rückruf-Thread fort. Der Unterschied zwischen diesen Optionen ist sehr groß: Der Thread, der den Rückruf verursacht, erwartet nicht, dass der Rückrufcode für immer ausgeführt wird, sondern lässt nur eine geringfügige Verzögerung zu, bevor das Steuerelement zurückkehrt. Auf der anderen Seite gibt Cats IO überhaupt keine solche Garantie: Der aufrufende Thread, der startende Rückruf, kann einfrieren und auf eine unbestimmte Zeit warten, wenn die Ausführungssteuerung zurückkehrt.
Frühere Versionen der konkurrierenden Datenstrukturen in Cats Effect ("Deferred", "Semaphore") setzten Effekte fort, die die Kontrolle über die Ausführung nicht an den aufrufenden Thread zurückgaben. Infolgedessen wurden Probleme im Zusammenhang mit Deadlocks und einem fehlerhaften Ausführungsplaner darin entdeckt. Obwohl all diese Probleme gefunden wurden, sind sie nur für wettbewerbsfähige Datenstrukturen in Cats Effect behoben.
Benutzercode, der einen ähnlichen Ansatz wie in Cats IO verwendet, wird in solche Probleme geraten, da solche Aufgaben nicht deterministisch sind und Fehler zur Laufzeit nur sehr selten auftreten können, was das Debuggen und die Problemerkennung zu einem schwierigen Prozess macht.
ZIO bietet sofort einen Deadlock-Schutz und einen normalen Taskplaner und lässt den Benutzer das Verhalten von Cats IO explizit auswählen (z. B. "unsafeRun" für "Promise", was zu einem wieder aufgenommenen asynchronen Effekt führte).
Obwohl keine der Lösungen für absolut alle Fälle geeignet ist und ZIO und Cats IO genügend Flexibilität bieten, um alle Situationen (auf unterschiedliche Weise) zu lösen, bedeutet die Auswahl von ZIO, dass Sie „Async“ ohne Sorgen verwenden und den Problemcode in „unsafeRun“ einfügen müssen. was bekanntermaßen Deadlock verursacht
7. Kompatibel mit der Zukunft
Die Verwendung von „Future“ aus der Scala-Standardbibliothek ist für eine große Anzahl von Codebasen Realität. ZIO wird mit einer "fromFuture" -Methode geliefert, die einen vorgefertigten Ausführungskontext bereitstellt:
ZIO.fromFuture(implicit ec =>
Wenn diese Methode verwendet wird, um Future in einen Effekt zu verpacken, kann ZIO festlegen, wo Future ausgeführt wird, und andere Methoden, wie z. B. evalOn, übertragen Future korrekt in den gewünschten Ausführungskontext. Cats IO akzeptiert "Future", das mit einem externen "ExecutionContext" erstellt wurde. Dies bedeutet, dass Cats IO die Ausführung von Future nicht gemäß den Anforderungen der evalOn- oder Shift-Methode verschieben kann. Darüber hinaus belastet dies den Benutzer mit der Bestimmung des Ausführungskontexts für Future, was eine enge Auswahl und eine separate Umgebung bedeutet.
Da der bereitgestellte ExecutionContext ignoriert werden kann, kann ZIO als Summe der Cats IO-Funktionen dargestellt werden, was im allgemeinen Fall eine reibungslosere und genauere Interaktion mit Future garantiert. Es gibt jedoch immer noch Ausnahmen.
8. Blockieren von E / A.
Wie im Artikel „
Thread Pool. Best Practices mit ZIO “Für Serveranwendungen sind mindestens zwei separate Pools erforderlich, um maximale Effizienz
zu erzielen :
- fester Pool für CPU / asynchrone Effekte;
- dynamisch, mit der Möglichkeit, die Anzahl der blockierenden Threads zu erhöhen.
Die Entscheidung, alle Effekte auf einen festen Thread-Pool auszuführen, führt eines Tages zu einem Deadlock, während das Auslösen aller Effekte auf einen dynamischen Pool zu Leistungseinbußen führen kann.
In der JVM bietet ZIO zwei Vorgänge, die Blockierungseffekte unterstützen:
- Operator "Blocking (Effekt"), der die Ausführung eines bestimmten Effekts im Pool blockierender Threads mit guten Voreinstellungen umschaltet, die bei Bedarf geändert werden können);
- «effectBlocking(effect)» , , .
, , , «blocking». , - , , «effectBlocking» , ZIO ( ).
Cats IO , . , «blocking», «evalOn», , , .
( ZIO) (, ), .
9.
, Scala, :
- «ReaderT»/ «Kleisli», ;
- «EitherT», ( «OptionT», «EitherT» «Unit» ).
, (, http4s «Kleisli» «OptionT»). («effect totation»), ZIO «reader» «typed error» ZIO. «reader» «typed error» , ZIO , . , «Task[A]», «reader» «typed errors».
ZIO () - . , ZIO , .
Cats IO . , , «reader» «typed errors» «state», «writer» , .
ZIO 8 Cats IO . , Scala .
10.
ZIO , , . , Scala, .
ZIO 2000 , «typed errors» , — 375 . Scala , . , , .
:
. , - , .
- . , . ZIO . Cats IO , , ZIO ( , ).
11.
ZIO , , - .
- , : «ZIO. succeed» «Applicative[F].pure», «zip» «Apply[F].product», «ZIO.foreach» «Traverse[F].traverse».
- (Cats, Cats Effect, Scalaz ).
- , ( «Runtime», Cats Effect - Cats Effect). — Cats IO.
- .
- . : "zip"/"zipPar", "ZIO.foreach"/"ZIO.foreachPar", "ZIO.succeed"/"ZIO.succeedLazy«.
- , «». ZIO IDE.
- Scala ZIO : «ZIO.fromFuture», «ZIO.fromOption», «ZIO.fromEither», «ZIO.fromTry».
- «».
, Scala, , ZIO , , , ZIO, . Cats IO , Cats.
, , , ( , , ).
12.
ZIO — - , .
:
- , «Ref», «Promise», «Queue», «Semaphore» «Stream» //;
- STM, , , ;
- «Schedule», ;
- «Clock», «Random», «Console» «System» , ;
- , .
- Cats IO . Cats IO , ( ) .
Fazit
Cats Effect Scala-, , .
, Cats Effect, , Cats Effect : Cats IO, Monix, Zio.
, . , , , : ZIO Cats Effect .
Scala — . , Scala. ScalaConf , 18 , John A De Goes .