Sollte das Erstellen zustandsbehafteter Objekte mit einem Effekttyp modelliert werden?
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 mitRef
. 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?
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?
Wenn Ihre Frage umformuliert werden kann als
Sind die zusätzlichen Vorteile (zusätzlich zu einer korrekt funktionierenden Implementierung mit einer "schwächeren Typklasse") der referenziellen Transparenz und der lokalen Argumentation ausreichend, um die Verwendung eines Effekttyps (der bereits für den Zugriff und die Mutation des Staates verwendet werden muss) auch für den Staat zu rechtfertigen? Schaffung ?
dann: Ja, absolut .
Um ein Beispiel zu geben, warum dies nützlich ist:
Folgendes funktioniert einwandfrei, obwohl die Erstellung von Diensten keine Auswirkungen hat:
val service = makeService(name)
for {
_ <- service.doX()
_ <- service.doY()
} yield Ack.Done
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.
for {
_ <- makeService(name).doX()
_ <- makeService(name).doY()
} yield Ack.Done
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.
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.