A construção de objetos com estado deve ser modelada com um tipo de efeito?
Se você já estiver usando um sistema de efeitos, provavelmente terá um Ref
tipo para encapsular com segurança o estado mutável.
Então eu digo: modele objetos com estadoRef
. Como a criação (e o acesso a) já é um efeito, isso também tornará a criação do serviço automática.
Isso limita sua pergunta original.
Se você deseja gerenciar manualmente o estado mutável interno com regularidade var
, deve certificar-se de que todas as operações que tocam nesse estado são consideradas efeitos (e provavelmente também são feitas com thread thread safe), o que é tedioso e propenso a erros. Isso pode ser feito, e eu concordo com a resposta da @ atl de que você não precisa estritamente tornar eficaz a criação do objeto com estado (contanto que possa viver com a perda da integridade referencial), mas por que não salvar o problema e abraçar as ferramentas do seu sistema de efeitos todo o caminho?
Eu acho que tudo isso é puro e determinístico. Apenas não é referencialmente transparente, pois a instância resultante é diferente a cada vez. Essa é uma boa hora para usar um tipo de efeito?
Se sua pergunta puder ser reformulada como
Os benefícios adicionais (além de uma implementação que funciona corretamente usando uma "classe de letra mais fraca") da transparência referencial e do raciocínio local são suficientes para justificar o uso de um tipo de efeito (que já deve estar em uso para acesso e mutação de estado) também para o estado criação ?
então: Sim, absolutamente .
Para dar um exemplo de por que isso é útil:
O seguinte funciona bem, mesmo que a criação do serviço não seja efetivada:
val service = makeService(name)
for {
_ <- service.doX()
_ <- service.doY()
} yield Ack.Done
Mas se você refatorar isso da seguinte forma, não receberá um erro em tempo de compilação, mas terá alterado o comportamento e provavelmente terá introduzido um bug. Se você tivesse declarado makeService
eficaz, a refatoração não faria a verificação de tipo e seria rejeitada pelo compilador.
for {
_ <- makeService(name).doX()
_ <- makeService(name).doY()
} yield Ack.Done
A atribuição do nome do método como makeService
(e com um parâmetro também) deve deixar bem claro o que o método faz e que a refatoração não era algo seguro, mas "raciocínio local" significa que você não precisa procurar nas convenções de nomenclatura e na implementação de makeService
para descobrir isso: qualquer expressão que não possa ser embaralhada mecanicamente (desduplicada, tornada preguiçosa, ansiosa, eliminada pelo código morto, paralelizada, atrasada, em cache, removida de um cache etc) sem alterar o comportamento ( ie não é "puro") deve ser digitado como eficaz.
delay
e retornar um F [Serviço] . Como um exemplo, veja ostart
método no IO , ele retorna um IO [Fiber [IO,?]] , Em vez da fibra simples .