Sollte bei Verwendung einer funktionalen Umgebung wie Scala und cats-effect
die Konstruktion zustandsbehafteter Objekte mit einem Effekttyp modelliert werden?
// not a value/case class
class Service(s: name)
def withoutEffect(name: String): Service =
new Service(name)
def withEffect[F: Sync](name: String): F[Service] =
F.delay {
new Service(name)
}
Die Konstruktion ist nicht fehlbar, daher könnten wir eine schwächere Typklasse wie verwenden Apply
.
// never throws
def withWeakEffect[F: Applicative](name: String): F[Service] =
new Service(name).pure[F]
Ich denke, all dies ist rein und deterministisch. Nur nicht referenziell transparent, da die resultierende Instanz jedes Mal anders ist. Ist das ein guter Zeitpunkt, um einen Effekttyp zu verwenden? Oder würde es hier ein anderes Funktionsmuster geben?
scala
functional-programming
scala-cats
cats-effect
Mark Canlas
quelle
quelle
delay
und einen F [Service] zurückgeben . Beispiel: Diestart
Methode für E / A gibt anstelle der einfachen Faser eine E / A [Fiber [IO ,?]] Zurück.Antworten:
Wenn Sie bereits ein Effektsystem verwenden, hat es höchstwahrscheinlich einen
Ref
Typ, der den veränderlichen Zustand sicher einkapselt.Also sage ich: modelliere Stateful Objects mit
Ref
. Da das Erstellen (sowie der Zugriff darauf) bereits ein Effekt ist, wird das Erstellen des Dienstes automatisch ebenfalls effektiv.Dies umgeht Ihre ursprüngliche Frage ordentlich.
Wenn Sie den internen veränderlichen Status manuell mit einem regulären Status verwalten möchten, müssen
var
Sie selbst sicherstellen, dass alle Vorgänge, die diesen Status berühren, als Auswirkungen betrachtet werden (und höchstwahrscheinlich auch threadsicher gemacht werden), was langwierig und fehleranfällig ist. Dies kann getan werden, und ich stimme der Antwort von @ atl zu, dass Sie die Erstellung des zustandsbehafteten Objekts nicht unbedingt effektiv machen müssen (solange Sie mit dem Verlust der referenziellen Integrität leben können), aber warum sparen Sie sich nicht die Mühe und die Umarmung die Werkzeuge Ihres Effektsystems den ganzen Weg?Wenn Ihre Frage umformuliert werden kann als
dann: Ja, absolut .
Um ein Beispiel zu geben, warum dies nützlich ist:
Folgendes funktioniert einwandfrei, obwohl die Erstellung von Diensten keine Auswirkungen hat:
Wenn Sie dies jedoch wie folgt umgestalten, wird beim Kompilieren kein Fehler angezeigt, aber Sie haben das Verhalten geändert und höchstwahrscheinlich einen Fehler eingeführt. Wenn Sie für
makeService
wirksam erklärt hätten, würde das Refactoring keine Typprüfung durchführen und vom Compiler abgelehnt werden.Zugegeben, die Benennung der Methode als
makeService
(und auch mit einem Parameter) sollte ziemlich klar machen, was die Methode tut und dass das Refactoring keine sichere Sache war, aber "lokales Denken" bedeutet, dass Sie nicht suchen müssen bei Namenskonventionen und der Implementierung von,makeService
um das herauszufinden: Jeder Ausdruck, der nicht mechanisch gemischt werden kann (dedupliziert, faul gemacht, eifrig gemacht, toter Code beseitigt, parallelisiert, verzögert, zwischengespeichert, aus einem Cache gelöscht usw.), ohne das Verhalten zu ändern ( dh ist nicht "rein") sollte als wirksam eingegeben werden.quelle
Worauf bezieht sich Stateful Service in diesem Fall?
Meinen Sie damit, dass es einen Nebeneffekt ausführt, wenn ein Objekt erstellt wird? Aus diesem Grund ist es besser, eine Methode zu verwenden, die den Nebeneffekt beim Starten Ihrer Anwendung ausführt. Anstatt es während des Baus laufen zu lassen.
Oder sagen Sie vielleicht, dass es innerhalb des Dienstes einen veränderlichen Zustand hat? Solange der interne veränderbare Zustand nicht freigelegt ist, sollte er in Ordnung sein. Sie müssen nur eine reine (referenziell transparente) Methode bereitstellen, um mit dem Dienst zu kommunizieren.
Um meinen zweiten Punkt zu erweitern:
Angenommen, wir erstellen eine In-Memory-Datenbank.
IMO, dies muss nicht effektiv sein, da das Gleiche passiert, wenn Sie einen Netzwerkanruf tätigen. Sie müssen jedoch sicherstellen, dass es nur eine Instanz dieser Klasse gibt.
Wenn Sie den Katzeneffekt verwenden
Ref
, würde ich normalerweiseflatMap
den Schiedsrichter am Einstiegspunkt ansprechen, damit Ihre Klasse nicht effektiv sein muss.OTOH, wenn Sie einen gemeinsam genutzten Dienst oder eine Bibliothek schreiben, die von einem statusbehafteten Objekt abhängig ist (z. B. mehrere Parallelitätsprimitive), und Sie nicht möchten, dass Ihre Benutzer sich darum kümmern, was initialisiert werden soll.
Dann muss es ja in einen Effekt eingewickelt werden. Sie könnten so etwas verwenden,
Resource[F, MyStatefulService]
um sicherzustellen, dass alles richtig geschlossen ist. Oder nur,F[MyStatefulService]
wenn es nichts zu schließen gibt.quelle
val neverRunningThisButStillMessingUpState = Task.pure(service.changeStateThinkingThisIsPure()).repeat(5)
) zu vermeidenpure
dass es referenziell transparent sein muss. Betrachten Sie zB ein Beispiel mit Future.val x = Future {... }
unddef x = Future { ... }
bedeutet etwas anderes. (Dies kann Sie beißen, wenn Sie Ihren Code umgestalten.) Dies ist jedoch bei Katzeneffekten, Monix oder Zio nicht der Fall.