03 O Livro do Ridgun
Funções, assinaturas de tipos e a arte de escrever código como uma tabela
Há uma pergunta que todo o programador faz na primeira vez que vê código funcional:
"Se aqui não há classes, nem objetos, nem variáveis que mudam de valor... então onde é que eu meto a minha lógica?"
Em funções. Mas não nessas funções de 200 linhas cheias de bandeiras booleanas que todos já sofremos alguma vez, e sim em funções pequenas, diretas e tão fáceis de ler que parecem uma tabela num quadro.
Vamos ver como se constroem.
3.1. Duas formas de apresentar uma função (Você escolhe o fato)
Em Ridgun toda a função começa com a sua assinatura de tipos (o que entra e o que sai após =>) e por baixo vão as suas cláusulas de implementação (com =).
Pode escrever o cabeçalho de duas maneiras:
Opção A: Direta ao assunto (sem prefixo)
aplicar desconto Int Int => Int
aplicar desconto preco percentagem = preco - (preco * percentagem / 100)
Opção B: Com etiqueta explícita (fn ::)
fn :: aplicar desconto Int Int => Int
aplicar desconto preco percentagem = preco - (preco * percentagem / 100)
"Olha lá, e porque é que existem as duas?" Porque gostos não se discutem:
- Há quem prefira que as funções comecem encostadas à margem esquerda sem escrever
fn ::de cada vez (afinal de contas, a seta=>já grita aos quatro ventos que essa linha é uma assinatura de tipos). - E há quem prefira ter uma coluna esquerda dedicada às etiquetas (
fn ::,op ::,type,import) para percorrer o ficheiro visualmente.
O compilador e o stable format entendem ambas na perfeição. Além disso, as palavras fn e op podem ser usadas livremente como parte do nome de qualquer função ou operador (por exemplo fn :: op, op :: fn ou op :: op), e se uma função devolver múltiplos valores, em modo canónico/estrito os tipos de saída escrevem-se separados por espaços após => (dividir com resto Int Int => Int Int).
3.2. A magia das colunas: porque é que os seus olhos descansam
Veja esta função que calcula o custo de um envio segundo o país e se é urgente:
custo envio String Bool Int => Int
custo envio "PT" False _ = 5
custo envio "PT" True _ = 12
custo envio _ False peso = 10 + (peso * 2)
custo envio _ True peso = 25 + (peso * 4)
Faça o teste: tape a metade direita do ecrã e olhe apenas para as três colunas de entrada:
- Primeira coluna (
String):"PT","PT",_,_. - Segunda coluna (
Bool):False,True,False,True. - Terceira coluna (
Int):_,_,peso,peso.
Vê algum buraco lógico? Nenhum: estão as 4 combinações possíveis mesmo à frente dos seus olhos, alinhadas verticalmente debaixo de String, Bool e Int. Num bloco de if / else tradicional teria de ler 18 linhas para ter a certeza de que não deixou nenhum caso solto.
3.3. Os tipos básicos e um presente para não contar zeros (_KB, _MB, _GB)
Os tipos escalares do Ridgun são diretos e sem surpresas:
| Tipo | O que é na realidade | Exemplos |
|---|---|---|
Int | Inteiro de 64 bits com sinal (i64) | 0, 42, -100, 0xFF00, 1_000_000 |
U64 | Inteiro de 64 bits sem sinal (u64) com aritmética modular e bit a bit nativa | u64 0, u64 0x6a09e667f3bcc908, a ^ b, a << u64 13 |
Float / Real | Decimal de precisão dupla (f64) | 3.14159, 0.001, -2.5 |
String | Cadeia de texto imutável | "Olá", 'caminho\bruto' |
Char | Código de carácter | Devolvido por str char at |
Bool | Booleano clássico | True, False |
() | Vazio (Unit, quando só imprime ou faz I/O) | () |
Cansado de escrever 65536 ou 1048576 e rezar para não ter engolido um dígito?
Em Ridgun os literais numéricos aceitam diretamente os sufixos _KB, _MB e _GB, além de sublinhados _ para separar grupos de algarismos:
tamanho buffer => Int
tamanho buffer = 64_KB -- O compilador traduz para 65536
limite ficheiro => Int
limite ficheiro = 16_MB -- O compilador traduz para 16777216
mascara octeto => Int
mascara octeto = 0xFF_00 -- Hexadecimal limpo e legível
3.4. Aspas duplas ("...") vs. Aspas simples ('...')
Em muitas linguagens as aspas simples e duplas fazem exatamente o mesmo (um desperdício). Em Ridgun cada uma tem uma missão claríssima:
1. Aspas duplas ("..."): Inteligentes e interpoladas
Interpretam escapes (\n, \t, \", \\) e avaliam qualquer variável ou expressão que coloque entre chavetas {...}:
resumo encomenda String Int Int => String
resumo encomenda item un preco =
"Encomendaste {un}x {item}. Total a pagar: {un * preco} euros."
(Truque: se precisar de escrever uma chaveta literal dentro de aspas duplas — por exemplo ao gerar um script de shell "$\{VAR:-default\}" ou um JSON —, pode escapá-la com barra invertida \{ e \} ou duplicá-la {{ e }}).
2. Aspas simples ('...'): Brutas e literais (Raw Strings)
O que escreve entre aspas simples sai tal e qual, sem tocar numa única barra invertida \, nem numa chaveta {}. Perfeito para quando gera código noutra linguagem, escreve comandos de shell ou trabalha com caminhos cheios de barras:
comando sed => String
comando sed = 's/\([0-9]+\)/[\1]/g'
A sua caixa de ferramentas para cadeias (String)
Unir cadeias é tão simples como somá-las com +: "Olá, " + nome. E quando precisa de fazer cirurgia fina sobre um texto, tem estas funções nativas prontas sem importar nada:
str len s$\rightarrow$ comprimento des.str char at s i$\rightarrow$ código numérico (Int) do carácter na posiçãoi(-1se sair fora dos limites).str slice s inicio fim$\rightarrow$ recorta desdeinicioatéfim(sem incluirfim).char from code c$\rightarrow$ converte um código numérico (Int, p. ex.10para\nou34para") numaStringde 1 carácter.str contains s agulhaestr starts with s prefixo$\rightarrow$ devolvemTrueouFalse.int to str nestr to int s$\rightarrow$ convertem de número para texto e vice-versa (ostr to intentende inclusive"0xFF"!).
3.5. Blocos de várias linhas, a regra universal do = e variáveis mut
Quando uma função não cabe numa única linha, faz uma quebra de linha depois do = e indenta o bloco.
Veja como se lê um bloco passo a passo:
calcular fatura Int Int => Int
calcular fatura base iva_pct =
impostos = base * iva_pct / 100
despesas_gestao = 5
echo "Detalhe -> Base:" base "Impostos:" impostos
base + impostos + despesas_gestao
Quatro regras de ouro que tornam os blocos em Ridgun limpos e uniformes:
- Imutável por omissão sem
letnemconst: escreveimpostos = ...e já tem a sua constante local imutável. Se por descuido tentar sobrescreverimpostos = 99mais abaixo no mesmo bloco, o compilador trava-o a fundo. - Variáveis mutáveis explícitas com
mut: quando realmente quer ir atualizando um acumulador local passo a passo dentro de uma função, declara-o commute depois reatribui-o livremente com=:
aplicar sobretaxas Int => Int
aplicar sobretaxas preco =
mut total = preco
total = total + 10
total = total * 2
total
(E se total guardar uma lista ou estrutura em memória, a política RMM2* do Ridgun soma +1 ao novo valor antes de descontar -1 ao antigo, libertando-o no instante se mais ninguém o usar).*
- A regra universal do
=: depois de qualquer=(numa função, num operador, numa variável localx =ou num ramo condicional) pode pôr sempre qualquer uma das três formas de bloco do Ridgun:
(nada): linhas sequenciais indentadas (como acima).|: uma árvore de guardas condicionais.||: um grafo de cálculos em paralelo com->.
- A última expressão é sempre o resultado:
base + impostos + despesas_gestaoé o que devolvecalcular fatura. Semreturn.
No Capítulo 04 vamos ver como tomar decisões complexas — incluindo árvores de decisão de vários níveis com |. — sem escrever jamais um if/else nem um ciclo while: bem-vindo ao mundo das guardas booleanas e da recursão de cauda de custo zero.