10 — Lambdas e Programação Funcional
Meta do capítulo: escrever funções anônimas, capturar variáveis e construir pipelines de dados. Referência:
../language-reference/closures.md. Exemplo:../../examples/functional.aura.
Formas de lambda
Uma lambda é um valor de função anônima, escrita com =>. Não há keyword lambda.
let double = x => x * 2
let square = (x) => x * x
let add = (a, b) => a + b
let get_answer = () => 42
| Forma | Exemplo |
|---|---|
| Um parâmetro, sem parênteses | x => x * 2 |
| Um parâmetro, parênteses | (x) => x * x |
| Múltiplos parâmetros | (a, b) => a + b |
| Nenhum | () => 42 |
| Corpo de expressão | (x) => x + 1 |
| Corpo de bloco | (x) => { let y = x + 1; return y } |
| Currying | (n) => (x) => x + n |
Um corpo de expressão vira uma lambda Python; um corpo de bloco é hoisted para uma função nomeada real (_aura_lambda_N), porque a lambda do Python não pode conter statements.
Parâmetros usam a gramática de def: anotados, com default, *args, **kwargs. Uma anotação é aceita e apagada; um tipo de retorno -> não é sintaxe de lambda.
Chamando lambdas
def main() {
let add = (a, b) => a + b
let blocky = (x) => {
let y = x + 1
return y * 2
}
print(add(2, 3)) // 5
print(blocky(4)) // 10
}
Closures
Uma lambda que lê um local envolvente o captura por referência:
def main() {
let base = 10
let add_base = (x) => x + base
print(add_base(5)) // 15
}
Currying — retornar uma lambda que captura um argumento:
def main() {
let make_adder = (n) => (x) => x + n
let add10 = make_adder(10)
print(add10(5)) // 15
}
Limitação: uma lambda de bloco que atribui a um local envolvente atualmente dispara E319 ('n' is used before it is declared) no checker, mesmo que a referência descreva emitir nonlocal. Captura somente leitura e currying estão verificados; para um contador, use um objeto mutável (uma lista de um elemento ou uma classe) em vez de mutar um local capturado.
Pegadinha: a captura é por referência, não um snapshot por iteração. Uma lambda criada em um loop vê o valor final da variável do loop. Vincule-a como um parâmetro default ((x, i = i) => x + i) para captura por iteração.
Funções de ordem superior
Lambdas são valores comuns, então funções as recebem e retornam. É assim que os helpers de coleção funcionam.
def apply_twice(f, x) {
return f(f(x))
}
def main() {
print(apply_twice((x) => x + 3, 1)) // 7
}
map / filter / reduce
Estes vêm de stdlib.collections e são auto-importados quando usados:
def main() {
let numbers = [1, 2, 3, 4, 5]
let doubled = map(numbers, (x) => x * 2) // [2, 4, 6, 8, 10]
let evens = filter(numbers, (x) => x % 2 == 0) // [2, 4]
let total = reduce(numbers, (a, b) => a + b, 0) // 15
print(doubled, evens, total)
}
O operador pipe |>
a |> f aplica f a a; a |> f(b) insere a como o primeiro argumento. Pipelines se leem da esquerda para a direita:
def main() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let result = numbers
|> filter((x) => x % 2 == 0)
|> map((x) => x * x)
|> reduce((acc, x) => acc + x, 0)
print(f"Sum of even squares: {result}") // 220
}
Quando o lado direito é uma função de coleção da stdlib (map, filter, reduce, take, drop), o import é injetado automaticamente. Pipe é o operador de expressão mais frouxo e encadeia da esquerda para a direita. Se o lado direito é um identificador nu, ele se torna f(left).
Composição
def main() {
let compose = (f, g) => (x) => g(f(x))
let inc = x => x + 1
let double = x => x * 2
print(compose(inc, double)(5)) // 12 — double(inc(5))
}
Sem açúcar de trailing-lambda
Aura não tem sintaxe de chamada trailing-lambda f { ... }. Um { ... } após uma chamada é ou um bloco separado ou um inicializador de struct, não um argumento final. Coloque a lambda dentro dos parênteses:
apply((x) => x + 1) // correct
// apply(1) { x => x + 1 } // NOT a trailing lambda
O que você aprendeu
- Lambdas
x => ...,(a, b) => ...,() => ..., com corpos de expressão ou de bloco. - Captura de leitura e currying; a limitação de captura mutável.
- Funções de ordem superior;
map/filter/reduce; o operador pipe. - Sem açúcar de trailing-lambda.