Pular para o conteúdo

O sistema de módulos

adopt e relay — o que é público, onde mora cada módulo, e como isso se compara a CommonJS e a ECMAScript.

Toda linguagem grande acaba precisando responder quatro perguntas sobre módulos: o que um arquivo oferece, onde mora o que ele pede, quantas vezes ele carrega e o que acontece quando dois se pedem em círculo. Esta seção responde as quatro, com o comportamento medido — não com a intenção.

As duas palavras:

dataforge
adopt Arcane.Math as Math          // o módulo inteiro, com apelido
adopt Arcane.Math.{sqrt, floor}    // só o que interessa
adopt {sqrt as raiz} from Arcane.Math
adopt ./util as U                  // um arquivo vizinho

relay somar, Ponto                 // o que ESTE arquivo oferece

Ao lado de CommonJS e ECMAScript#

A tabela existe para quem chega de uma das duas. Onde a DataForge difere, ela difere de propósito, e a coluna da direita diz por quê.

PerguntaCommonJSECMAScript (ESM)DataForge
importarrequire('x')import x from 'x'adopt x as X
exportarmodule.exports =exportrelay
export padrãosim (module.exports)sim (export default)não existe
quando resolveem execuçãoantes de executarem execução, e o check confere antes
carrega quantas vezesumaumauma
o que é público sem declararnadanadatudo
ciclodevolve o parcialvive com TDZerro
import dinâmicorequire() em qualquer lugarimport()não existe
ordem de exportirrelevanteiçadodepois da declaração

Tudo é público até você dizer o contrário#

Um arquivo sem relay oferece tudo o que declara no topo. É o oposto do padrão de CommonJS e ESM, e a razão é o arquivo pequeno: obrigar um relay num módulo de três ações é cerimônia sem ganho.

aberto.df
interno := 42

action publica():
    yield "oi"
dataforge
adopt ./aberto as A
out A.publica(), A.interno       // oi 42 — os dois visíveis

O primeiro `relay` fecha a porta. A partir dele, o arquivo declara o que exporta, e o resto vira interno:

dataforge
interno := 42

action publica():
    yield "oi"

relay publica          // agora 'interno' não atravessa mais

Essa é também a fronteira que o analisador usa: um módulo com relay diz o que é contrato, e A.interno passa a ser acusado antes de rodar.

`relay` vem depois, e isso não é detalhe#

Em JavaScript, export function f(){} funciona em qualquer posição, porque a declaração é içada. Aqui o relay os nomes que já existem:

dataforge
relay usar              // erro: 'usar' ainda não existe

action usar():
    yield 1
text
erro: 'usar' is not defined
  dica: define it before the 'relay', or remove it from the list

A forma certa é o relay no fim do arquivo, que é também onde ele se lê melhor — a última linha responde "o que este arquivo oferece?" sem obrigar a percorrer tudo.

Um módulo carrega uma vez, e o estado é compartilhado#

Como em CommonJS e em ESM. Dois arquivos que adotam o mesmo módulo recebem o mesmo módulo — e o estado dele é único no programa:

contador.df
out "carregando contador.df"
total := 0

action somar():
    total += 1
    yield total

relay somar
a.df
adopt ./contador as C

action usar():
    yield C.somar()

relay usar
main.df
adopt ./contador as C
adopt ./a as A

out C.somar()      // 1
out A.usar()       // 2   o MESMO contador
out C.somar()      // 3
saída
carregando contador.df
1
2
3

A mensagem de carga aparece uma vez. É o que torna um módulo um bom lugar para configuração e um lugar perigoso para estado mutável: total acima é global ao programa, e duas rotas de um servidor escrevendo nele perdem atualizações — ver concorrência.

Por onde seguir#