Efeitos e propagação
O efeito que roda ao nascer, o lote, e o losango que entregava um valor que nunca existiu.
O efeito roda uma vez ao ser criado#
action mostrar():
linhas.append($"total R$ {total.ler()}")
steady vigia := R.efeito(mostrar) // ja rodou uma vez
itens.escrever([...]) // roda de novo
vigia.parar() // e para de rodarSem isso, quem escreve R.efeito(…) vê a tela vazia até a primeira mudança — e conclui que o efeito não funciona. A alternativa seria chamá-lo à mão depois de criar, o que se esquece exatamente uma vez.
A propagação tem duas fases#
Esta é a decisão que mais importa do módulo, e ela veio de um defeito medido. Num losango — c lê a e b, e b lê a — marcar e avisar numa fase só entrega um valor que nunca existiu.
a := R.sinal(1)
b := R.derivado(lambda => a.ler() * 2)
c := R.derivado(lambda => a.ler() + b.ler())
R.efeito(lambda => vistos.append(c.ler()))
a.escrever(5)
// antes: [3, 7, 15] <- o 7 nunca foi verdade
// agora: [3, 15]Hoje a marcação percorre o grafo inteiro primeiro, e só depois os efeitos e ouvintes rodam. Daí saem três regras:
| Regra | Sem ela |
|---|---|
| o aviso pertence à propagação, e não ao recálculo | uma simples leitura disparava efeito de terceiros |
| o efeito alcançado por dois caminhos roda uma vez | a tela redesenhava duas vezes por mudança |
a dedup é pelo objeto, e não por id() | id() só é único entre objetos vivos |
| uma escrita dentro de um efeito abre a próxima onda | a fila cresceria enquanto é percorrida |
Lote#
action tres_escritas():
itens.escrever([...])
cupom.escrever(0.0)
itens.escrever([...])
R.lote(tres_escritas) // UMA notificacaoSem ele, mudar três sinais que alimentam o mesmo derivado faz o efeito rodar três vezes — e as duas primeiras mostram um estado intermediário que nunca deveria aparecer na tela.
Se a ação falhar no meio, os avisos saem mesmo assim: a escrita já aconteceu, e engolir a notificação deixaria a tela mostrando um estado que não é mais o do programa.