Diretivas por arquivo que ajustam como Bern se comporta - opte por regras mais rígidas ou mais flexíveis, um arquivo de cada vez.
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.
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.
| Pragma | Efeito |
|---|---|
impure-lists | listas podem conter tipos de elementos misturados |
impure-sets | operações de conjunto mantêm duplicatas |
strict-types | proíbe coerção implícita entre string/número |
typed | Bern 2.1 ativa declarações de variável com tipo |
strict-arithmetic | divisão por zero é erro, não NaN |
safe-index | indexação fora dos limites retorna undefined |
start-on-one | a indexação começa em 1 |
immutable | reatribuir uma variável existente é erro |
no-undefined | ler uma variável não definida é erro |
no-written-operators | libera os nomes de operadores em palavras para variáveis |
abort-on-error | falha em erros de execução em vez de valores de erro |
partial | permite funções não exaustivas |
no-curry | desativa o currying automático |
no-eval | expressões soltas não imprimem automaticamente |
show-types | valores impressos automaticamente mostram seu tipo |
main | executa main() após o carregamento do arquivo |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.