Pular para o conteúdo

Carga, ordem e ciclos

Quando o corpo de um módulo executa, o que ele deixa para trás, e por que um ciclo é erro aqui.

Um adopt executa o arquivo, de cima a baixo, uma vez. Tudo o que estiver no topo dele roda — inclusive o que imprime, o que abre arquivo e o que conecta em banco.

O que roda, e quando#

dataforge
// config.df — tudo isto roda no primeiro 'adopt'
out "lendo a configuração"
steady PORTA := 8000
conexao := abrir_banco()          // acontece AQUI, não no primeiro uso

action porta():
    yield PORTA

relay porta

Efeito colateral no topo de um módulo é a causa mais comum de "por que meu teste abre conexão?". A regra prática é a mesma de qualquer linguagem com carga única: no topo, só declaração e constante; o que custa fica dentro de uma ação.

Ciclo é erro, e não meia resposta#

As outras linguagens escolheram conviver com o ciclo: CommonJS devolve o módulo pela metade, e ESM deixa o nome numa zona morta onde lê-lo é erro de execução. As duas transformam um problema de arquitetura num bug intermitente.

x.df
adopt ./y as Y
action daqui():
    yield "x"
relay daqui
y.df
adopt ./x as X
action dali():
    yield "y"
relay dali
text
erro[DF0501]: Circular import: x.df → y.df → x.df.
Break the cycle by moving the shared part into a third module.

A cadeia inteira aparece. Um ciclo de quatro arquivos é impossível de quebrar sem saber por onde ele passa, e a busca é em largura para achar o ciclo mais curto — que é o mais fácil de romper.

O `check` acha o ciclo antes de rodar#

O ciclo estourava só em execução, no primeiro adopt, e o check passava limpo num projeto que não sobe:

bash
$ dataforge check x.df
x.df:1:1: erro: circular import: x.df  y.df  x.df
    sugestão: move the shared part into a third module

Como quebrar um ciclo#

  1. Mova o compartilhado para um terceiro módulo. Se x e y se pedem, quase sempre é porque ambos precisam de um tipo ou de uma constante que não é de nenhum dos dois.
  2. Inverta a dependência. Quem sabe como fazer não deveria conhecer quem manda fazer: passe a ação como argumento em vez de importar o chamador.
  3. Junte os dois. Dois arquivos que se pedem em círculo frequentemente são um só arquivo que alguém dividiu cedo demais.

Ordem de execução, num programa com módulos#

text
1. o interpretador lê o arquivo de entrada
2. cada 'adopt' do topo executa o módulo pedido, na ordem em que aparece
3. o módulo, por sua vez, executa os 'adopt' dele — em profundidade
4. um módulo já carregado NÃO executa de novo: devolve o mesmo objeto
5. o corpo do arquivo de entrada roda
6. os 'defer' escritos no topo rodam no fim, inclusive se houve erro

Por onde seguir#