La costruzione di oggetti con stato dovrebbe essere modellata con un tipo di effetto?
Se stai già utilizzando un sistema di effetti, molto probabilmente ha un Ref
tipo per incapsulare in modo sicuro lo stato mutabile.
Quindi dico: modella oggetti con stato conRef
. Poiché la creazione (oltre all'accesso a) di questi è già un effetto, questo renderà automaticamente efficace anche la creazione del servizio.
In questo modo, la tua domanda originale va di pari passo.
Se si desidera gestire manualmente uno stato mutabile interno con un regolare, var
è necessario assicurarsi da soli che tutte le operazioni che toccano questo stato siano considerate effetti (e molto probabilmente anche rese thread-safe), che è noioso e soggetto a errori. Questo può essere fatto, e sono d'accordo con la risposta di @ atl che non devi rigorosamente rendere efficace la creazione dell'oggetto stateful (purché tu possa vivere con la perdita di integrità referenziale), ma perché non risparmiarti il disturbo e abbracciarti gli strumenti del tuo sistema di effetti fino in fondo?
Immagino che tutti questi siano puri e deterministici. Semplicemente non referenzialmente trasparente poiché l'istanza risultante è diversa ogni volta. È un buon momento per usare un tipo di effetto?
Se la tua domanda può essere riformulata come
I vantaggi aggiuntivi (oltre a un'implementazione correttamente funzionante che utilizza una "classe di caratteri più debole") della trasparenza referenziale e del ragionamento locale sono sufficienti per giustificare l'uso di un tipo di effetto (che deve essere già in uso per l'accesso allo stato e la mutazione) anche per lo stato creazione?
allora: Sì, assolutamente .
Per fare un esempio del perché questo è utile:
Funziona bene anche se la creazione del servizio non ha effetto:
val service = makeService(name)
for {
_ <- service.doX()
_ <- service.doY()
} yield Ack.Done
Ma se si esegue il refactoring come di seguito non si otterrà un errore in fase di compilazione, ma si sarà modificato il comportamento e molto probabilmente sarà stato introdotto un bug. Se fosse stato dichiarato makeService
efficace, il refactoring non verificherebbe il controllo del tipo e sarebbe stato respinto dal compilatore.
for {
_ <- makeService(name).doX()
_ <- makeService(name).doY()
} yield Ack.Done
Concedere la denominazione del metodo come makeService
(e anche con un parametro) dovrebbe chiarire cosa fa il metodo e che il refactoring non era una cosa sicura da fare, ma "ragionamento locale" significa che non devi guardare alle convenzioni di denominazione e all'implementazione di makeService
capirlo: qualsiasi espressione che non può essere mescolata meccanicamente (deduplicata, resa pigra, resa desiderosa, codice morto eliminato, parallelizzata, ritardata, memorizzata nella cache, eliminata da una cache ecc.) senza cambiare comportamento ( cioè non è "puro") dovrebbe essere digitato come efficace.
delay
e restituire un F [Servizio] . Ad esempio, vedere ilstart
metodo su IO , restituisce un IO [Fibra [IO,?]] , Anziché la fibra semplice .