Pragmas Bern 2.0

Diretivas por arquivo que ajustam como Bern se comporta - opte por regras mais rígidas ou mais flexíveis, um arquivo de cada vez.

O que é um pragma?

Um pragma é um comentário mágico que coloca o interpretador em um modo mais rígido ou mais flexível. Escreva-o perto do topo do arquivo, um por linha:

{--! strict-types !--}
{--! immutable !--}

Pragmas têm escopo de arquivo, não global. O conjunto de pragmas sempre pertence ao arquivo cujo código está executando no momento: cada função lembra os pragmas do arquivo que a definiu, então uma biblioteca que declara {--! impure-lists !--} tem listas impuras dentro do seu próprio código sem impô-las ao programa que a importa - e vice-versa.

Nomes desconhecidos são ignorados com um aviso. Erre o nome de um pragma e Bern imprime Warning: unknown pragma '…' e segue em frente, então um erro de digitação nunca muda o comportamento silenciosamente.

O conjunto completo está resumido na tabela abaixo; as seções seguintes explicam cada um com um exemplo e quando usá-lo.

PragmaEfeito
impure-listslistas podem conter tipos de elementos misturados
impure-setsoperações de conjunto mantêm duplicatas
strict-typesproíbe coerção implícita entre string/número
typedBern 2.1 ativa declarações de variável com tipo
strict-arithmeticdivisão por zero é erro, não NaN
safe-indexindexação fora dos limites retorna undefined
start-on-onea indexação começa em 1
immutablereatribuir uma variável existente é erro
no-undefinedler uma variável não definida é erro
no-written-operatorslibera os nomes de operadores em palavras para variáveis
abort-on-errorfalha em erros de execução em vez de valores de erro
partialpermite funções não exaustivas
no-currydesativa o currying automático
no-evalexpressões soltas não imprimem automaticamente
show-typesvalores impressos automaticamente mostram seu tipo
mainexecuta main() após o carregamento do arquivo

Coleções

{--! impure-lists !--}

Por padrão uma lista é homogênea: todo elemento deve compartilhar um tipo, e misturá-los é erro. Este pragma remove essa regra, então uma lista pode conter valores de tipos diferentes.

-- Sem o pragma:
[1, "two", 3.0]
-- Error: os elementos da lista devem ter o mesmo tipo

-- Com {--! impure-lists !--}:
mixed = [1, "two", 3.0]
:> mixed          -- 3
mixed[0]          -- 1

Use quando estiver modelando registros de dados mistos - um registro CSV, uma tupla tipo JSON, ou um payload heterogêneo - e a regra de um-só-tipo atrapalhar.

{--! impure-sets !--}

Operações de conjunto normalmente removem duplicatas. Este pragma faz a união manter todas as ocorrências, transformando conjuntos em multiconjuntos pela duração do arquivo.

-- Sem o pragma:
{1, 2, 3} <| {2, 3, 4}     -- {1, 2, 3, 4}  (4 elementos)

-- Com {--! impure-sets !--}:
combined = {1, 2, 3} <| {2, 3, 4}
:> combined                 -- 6  (duplicatas mantidas)

Use quando realmente quiser semântica de bag/multiconjunto - contar ocorrências ou concatenar sem a remoção automática de duplicatas.

Tipos

{--! strict-types !--}

Bern faz coerção entre tipos de bom grado - somar um número a uma string converte o número em texto, por exemplo. Este pragma proíbe essa coerção implícita, então uma mistura de tipos vira um erro que você resolve explicitamente.

-- Sem o pragma:
"resposta: " + 42       -- "resposta: 42"

-- Com {--! strict-types !--}:
"resposta: " + 42       -- Error: o pragma strict-types proíbe coerção implícita
"resposta: " + to_str(42)   -- "resposta: 42"  (converta de propósito)

Use quando quiser que misturas acidentais de string/número apareçam como erro em vez de um valor convertido silenciosamente.

{--! typed !--} Bern 2.1

Ativa declarações de variável com tipo usando nome :: Tipo = valor. O valor é verificado contra o tipo declarado quando a atribuição executa; uma incompatibilidade é um erro fatal. Sem o pragma a sintaxe fica inativa e :: mantém seu significado usual de operador typeof.

{--! typed !--}

x :: Integer   = 10
a :: Character = 'a'
name :: String = "bern"

bad :: Integer = 'a'
-- Error: type mismatch for 'bad': declared Integer but value is Character

O tipo declarado aceita os nomes canônicos do typeof (Integer, Double, Boolean, Character, String, List, Set, Object), os apelidos Int, Float, Char, Bool, Text, qualquer nome de tipo ADT, e Auto / Any para "aceitar qualquer tipo que for passado".

Use quando algumas variáveis-chave carregam uma invariante que vale documentar e impor, enquanto o resto do arquivo permanece com tipagem dinâmica. Veja a seção Tipos para a referência completa.

Números & indexação

{--! strict-arithmetic !--}

Divisão por zero normalmente resulta em NaN ("não é um número"), que então contamina silenciosamente a aritmética seguinte. Este pragma a transforma em um erro imediato.

-- Sem o pragma:
1 / 0       -- NaN

-- Com {--! strict-arithmetic !--}:
1 / 0       -- Error: divisão por zero

Use quando uma divisão por zero acidental deve parar o programa na origem em vez de espalhar um NaN pelos resultados.

{--! safe-index !--}

Indexar além do fim de uma lista ou string normalmente é um erro. Com este pragma, um índice fora dos limites retorna undefined, então uma busca nunca falha.

-- Sem o pragma:
[1, 2, 3][99]     -- Error (um valor de erro)

-- Com {--! safe-index !--}:
[1, 2, 3][99]     -- undefined

Use quando quiser buscas tolerantes - sondar posições opcionais e tratar "ausente" como undefined em vez de lidar com um erro.

{--! start-on-one !--}

Bern indexa a partir de 0 por padrão. Este pragma desloca a indexação para começar em 1, então o primeiro elemento fica na posição 1.

-- Sem o pragma:
[10, 20, 30][0]   -- 10

-- Com {--! start-on-one !--}:
[10, 20, 30][1]   -- 10

Use quando estiver portando código 1-baseado ou trabalhando em um domínio (matrizes, números de linha, planilhas) onde a indexação a partir de 1 lê mais naturalmente.

Variáveis & nomes

{--! immutable !--}

Por padrão uma variável pode ser reatribuída livremente. Este pragma faz reatribuir um nome existente ser um erro - toda ligação é de escrita única.

{--! immutable !--}

x = 1
x = 2
-- Error: não é possível reatribuir 'x' (o pragma immutable proíbe)

Use quando quiser que constantes permaneçam constantes e para pegar sombreamento ou reuso acidental de um nome.

{--! no-undefined !--}

Ler um nome que nunca foi atribuído normalmente produz undefined. Este pragma transforma isso em erro, então uma variável não definida é pega imediatamente.

-- Sem o pragma:
print(nome_errado)    -- undefined

-- Com {--! no-undefined !--}:
print(nome_errado)    -- Error: 'nome_errado' não está definido

Use quando quiser que variáveis com erro de digitação ou esquecidas falhem alto em vez de passarem como undefined.

{--! no-written-operators !--}

Bern aceita operadores em palavras (plus, minus, and, or, length, not, …) como apelidos dos simbólicos, o que reserva esses nomes. Este pragma desliga os operadores em palavras, liberando os nomes para uso como variáveis comuns.

-- Sem o pragma:
length = 5          -- Error: 'length' é um operador em palavra

-- Com {--! no-written-operators !--}:
length = 5          -- ok; use :> para o tamanho em vez da palavra `length`
plus = 10           -- também ok

Use quando um nome como length, and ou plus for o nome de variável natural para seus dados e você não precisar das grafias em palavra dos operadores.

Erros & funções

{--! abort-on-error !--}

Por padrão um erro de execução vira um valor que você pode inspecionar com is_error, e o programa continua rodando (erros como valores). Este pragma restaura o comportamento clássico: um erro derruba o programa na hora.

-- Sem o pragma:
result = head([])
is_error(result)    -- true; o programa continua

-- Com {--! abort-on-error !--}:
head([])            -- derruba imediatamente com o erro

Use quando preferir falhar rápido - um script onde um erro deve parar tudo, não fluir silenciosamente como valor.

{--! partial !--}

Uma função de um argumento cujas cláusulas não cobrem todos os casos é normalmente rejeitada como não exaustiva. Este pragma permite tais funções parciais; chamar um caso não coberto ainda é um erro em tempo de execução.

-- Sem o pragma:
def describe(Num(n)) -> "número"
describe(Bool(true))
-- Error: a função 'describe' não é exaustiva
--        (adicione uma cláusula coringa, ou use o pragma `partial`)

-- Com {--! partial !--}: a definição é aceita como está.

Use quando estiver prototipando, ou uma função for intencionalmente definida só para algumas formas e você aceitar o risco em tempo de execução.

{--! no-curry !--}

Bern faz currying automático: chamar uma função com menos argumentos do que ela espera retorna uma nova função aguardando o resto. Este pragma desativa isso, então toda chamada deve passar todos os argumentos.

def add(x, y) -> x + y

-- Sem o pragma:
add5 = add(5)       -- uma função esperando mais um argumento
add5(3)             -- 8

-- Com {--! no-curry !--}:
add(5)              -- Error: 'add' espera 2 argumentos

Use quando preferir que um argumento faltando seja reportado como engano em vez de produzir silenciosamente uma função parcialmente aplicada.

Saída & ponto de entrada

{--! no-eval !--}

No nível superior, uma expressão solta imprime seu resultado automaticamente. Este pragma suprime isso, então só chamadas explícitas de print(...) produzem saída.

-- Sem o pragma:
2 + 2               -- imprime 4

-- Com {--! no-eval !--}:
2 + 2               -- não imprime nada
print(2 + 2)        -- imprime 4

Use quando um arquivo for um módulo ou script que não deve jogar valores intermediários no console - você controla a saída por print.

{--! show-types !--}

Quando um valor impresso automaticamente é exibido, este pragma acrescenta seu tipo, na forma valor : Tipo.

-- Sem o pragma:
42                  -- 42

-- Com {--! show-types !--}:
42                  -- 42 : Integer
"hi"                -- hi : List

Use quando estiver explorando no REPL ou depurando e quiser ver de relance qual o tipo de cada resultado.

{--! main !--}

Por padrão um arquivo roda de cima para baixo. Este pragma adicionalmente chama main() assim que o arquivo termina de carregar, dando ao programa um ponto de entrada convencional.

{--! main !--}

def greet(name) -> "Olá, " + name

def main() do
    print(greet("Bern"))
end
-- Após carregar, main() roda e imprime: Olá, Bern

Use quando quiser um ponto de entrada explícito - definindo auxiliares e um main no começo, e deixando o pragma dispará-lo.