¿Se debe modelar la construcción de objetos con estado con un tipo de efecto?
Si ya está utilizando un sistema de efectos, lo más probable es que tenga un Ref
tipo para encapsular de forma segura el estado mutable.
Por eso digo: modelar objetos con estado conRef
. Dado que la creación de (así como el acceso a) ya es un efecto, esto también hará que la creación del servicio también sea efectiva.
Esto deja de lado tu pregunta original.
Si desea administrar manualmente el estado mutable interno con regularidad var
, debe asegurarse de que todas las operaciones que toquen este estado se consideren efectos (y muy probablemente también sean seguros para subprocesos), lo cual es tedioso y propenso a errores. Esto se puede hacer, y estoy de acuerdo con la respuesta de @ atl de que no tiene que hacer estrictamente efectiva la creación del objeto con estado (siempre y cuando pueda vivir con la pérdida de integridad referencial), pero ¿por qué no ahorrarse el problema y abrazarlo? ¿Las herramientas de su sistema de efectos hasta el final?
Supongo que todos estos son puros y deterministas. Simplemente no es referencialmente transparente ya que la instancia resultante es diferente cada vez. ¿Es ese un buen momento para usar un tipo de efecto?
Si su pregunta puede reformularse como
¿Son los beneficios adicionales (además de una implementación que funciona correctamente utilizando una "clase de tipo más débil") de transparencia referencial y razonamiento local lo suficiente como para justificar el uso de un tipo de efecto (que ya debe estar en uso para el acceso y la mutación del estado) también para el estado creación?
entonces: sí, absolutamente .
Para dar un ejemplo de por qué esto es útil:
Lo siguiente funciona bien, aunque la creación del servicio no tenga efecto:
val service = makeService(name)
for {
_ <- service.doX()
_ <- service.doY()
} yield Ack.Done
Pero si refactoriza esto como se muestra a continuación, no obtendrá un error en tiempo de compilación, pero habrá cambiado el comportamiento y probablemente habrá introducido un error. Si hubiera declarado makeService
efectivo, la refactorización no se verificaría por tipo y sería rechazada por el compilador.
for {
_ <- makeService(name).doX()
_ <- makeService(name).doY()
} yield Ack.Done
Concedido a la denominación del método como makeService
(y también con un parámetro) debería dejar bastante claro lo que hace el método y que la refactorización no era algo seguro, pero el "razonamiento local" significa que no tiene que buscar en convenciones de nomenclatura y la implementación de makeService
para resolverlo: cualquier expresión que no se pueda mezclar mecánicamente (deduplicado, perezoso, ansioso, eliminación de código muerto, paralelo, retrasado, almacenado en caché, purgado de un caché, etc.) sin cambiar el comportamiento ( es decir, no es "puro") debe escribirse como eficaz.
delay
devolver un F [Servicio] . Como ejemplo, vea elstart
método en IO , devuelve un IO [Fiber [IO,?]] , En lugar de la fibra normal .