A linguagem de programação Racket não é interessante por sua sintaxe. O que a torna interessante é que ela foi projetada para construir linguagens, não apenas para escrever programas.

Este artigo pula o "Hello World" e exercícios simples, indo direto às ideias que tornam o Racket distinto, além de alguns scripts úteis para sistemas.

Escopo deste guia: Não é um manual de referência. Abrange um conjunto selecionado de ideias centrais, como expressões, funções de primeira classe, map/filter/fold, pattern matching, macros, contratos e alguns scripts para Linux. Para mais detalhes, leia o Racket Guide e o Racket Reference.

Uma nota sobre require: Um exemplo abaixo usa (require racket/contract). Se seu arquivo começa com #lang racket (em vez do menor #lang racket/base), isso já está incluído, e o require explícito é redundante. Muitas pessoas o mantêm mesmo assim, para clareza sobre as dependências do arquivo.

Configuração do Ambiente de Programação Racket

Você não precisa de nada sofisticado, apenas o próprio Racket.

1. Instalar a Linguagem Racket

Baixe o instalador para sua plataforma em download.racket-lang.org (a versão estável atual é v9.2). No Linux, você também pode instalar via seu gerenciador de pacotes, mas o instalador oficial geralmente oferece uma versão mais recente.

Publicamos um guia dedicado que explica diferentes formas de instalar o Racket no Linux. Confira o link a seguir:

  • Instalar a Linguagem de Programação Racket no Linux

Após a instalação, verifique a versão do Racket:

racket --version

2. Duas formas de executar os exemplos de código abaixo

Opção A: como um arquivo de script (o que a maioria dos exemplos neste guia assume):

Salve um trecho em um arquivo, começando com a linha #lang racket mostrada nos exemplos que a incluem (alguns trechos são fragmentos para serem colados em um arquivo existente; adicione #lang racket como primeira linha se estiver faltando):

#lang racket
(displayln (+ 2 3))

Depois execute no terminal:

racket my-file.rkt

Opção B: no REPL (mais rápido para testar expressões individuais):

racket

Isso abre um prompt interativo (>) onde você pode colar qualquer expressão deste guia e ver o resultado imediatamente:

> (map add1 '(1 2 3))
'(2 3 4)

Digite (exit) ou pressione Ctrl++D para sair do REPL.

3. Opcional: DrRacket (o IDE incluído)

O instalador também inclui o DrRacket, um editor gráfico com um botão "Run" e um painel de interações abaixo do código. Geralmente é a forma mais fácil de experimentar enquanto trabalha nos exemplos, especialmente os de múltiplas linhas. Basta abri-lo, colar um trecho com #lang racket no topo e clicar em Run.

Vamos começar.

1. Código é Dado

Diferente de C ou Python, o código-fonte Racket é uma estrutura de dados, especificamente listas aninhadas. Por exemplo, (+ 2 3)

(+ 2 3)

é literalmente uma lista cujo primeiro elemento é o símbolo + (que se refere a uma função), e o restante são argumentos: +, 2, 3.

Como código é dado, você pode construí-lo programaticamente e avaliá-lo. Note que este exemplo precisa ser executado no REPL, não como script. eval usa o namespace atual, e um script executado com #lang racket começa com um namespace vazio, então isso levantaria um erro de identificador não vinculado em vez de retornar 30.

(define expr '(+ 10 20))
(eval expr)

Saída de exemplo:

30

Se preferir executar isso como script em vez de no REPL, forneça um namespace explícito ao eval:

#lang racket
(define expr '(+ 10 20))
(eval expr (make-base-namespace))

Copie o código acima em um arquivo, por exemplo my-file.rkt, e execute-o com o comando:

racket my-file.rkt

Isso imprimirá 30.

Essa única ideia, código como dado, é o que torna as macros possíveis mais adiante.

2. Tudo é uma Expressão

Diferente de C, C++ ou Python, quase toda forma em Racket retorna um valor. Isso é verdade até mesmo para if.

# Python
if x > 10:
y = 5
else:
y = 20

se torna

(define y (if (> x 10) 5 20))

Da mesma forma, begin executa uma lista de expressões em ordem e retorna o valor da última.

(define result
(begin
(displayln "Calculating...")
(+ 4 7)))

Saída de exemplo:

Calculating...

result agora é 11. Como toda expressão retorna um valor, você pode combinar expressões diretamente em vez de criar variáveis extras apenas para armazenar um valor antes de usá-lo.

3. Funções são Valores de Primeira Classe

Funções podem ser armazenadas, passadas e retornadas como qualquer outro valor.

(define (square x) (* x x))
(define f square)
(f 8)
64

Funções anônimas funcionam da mesma forma:

(map (lambda (x) (* x x)) '(1 2 3 4 5))

Saída de exemplo:

'(1 4 9 16 25)

Você pode até colocar funções em uma lista:

(list add1 sub1 sqrt)
'(#<procedure:add1> #<procedure:sub1> #<procedure:sqrt>)

Nota: o texto exato mostrado para cada procedimento pode parecer ligeiramente diferente dependendo da sua versão do Racket, mas sempre mostrará que a lista contém funções, não números ou texto.

4. Programação de Ordem Superior: map, filter, fold

Em vez de escrever loops explícitos, pense em termos de transformações sobre coleções. Essas três operações resolvem uma quantidade surpreendente de problemas.

Map - transforma cada elemento:

(map string-length '("Linux" "Kernel" "Racket"))

Saída de exemplo:

'(5 6 6)

Filter - mantém elementos que correspondem a um predicado:

(filter even? '(1 2 3 4 5 6 7 8))

Saída de exemplo:

'(2 4 6 8)

Fold - combina elementos em um único resultado:

(foldl + 0 '(1 2 3 4 5))    ; sum      -> 15
(foldl * 1 '(1 2 3 4))      ; product  -> 24
(foldl max 0 '(2 8 5 12 4)) ; maximum  -> 12

5. Recursão e Chamadas de Cauda

Racket possui formas de loop, como for e do, mas muitos programas idiomáticos em Racket usam recursão em vez de loops, especialmente ao trabalhar com listas.

(define (factorial n)
(if (zero? n)
1
(* n (factorial (sub1 n)))))
(factorial 5)

Saída de exemplo:

120

Racket também garante chamadas de cauda adequadas. Isso significa que quando uma função chama a si mesma como o último passo, o Racket transforma essa chamada em um loop em vez de adicionar uma nova camada à pilha de chamadas. Por causa disso, a função abaixo pode processar com segurança milhões de elementos sem estourar a memória.

(define (sum lst)
(define (loop lst acc)
(if (empty? lst)
acc
(loop (rest lst) (+ acc (first lst)))))
(loop lst 0))

6. Named Let (Recursão que se Lê como um Loop)

let loop permite escrever iteração recursiva sem uma função separada no nível superior:

(let loop ([i 0])
(when (< i 5)
(displayln i)
(loop (add1 i))))

Saída de exemplo (cada chamada displayln imprime sua própria linha):

0
1
2
3
4

Isso se parece com um loop, mas por baixo é realmente recursão. Como a chamada a loop está em posição de cauda, o Racket a transforma automaticamente em um loop real.

7. Pattern Matching

Pattern matching é um dos melhores recursos do Racket. Ele vem de racket/match, que já está incluído quando você usa #lang racket, então você não precisa adicionar uma linha require extra.

Desestruturação simples:

(match '(1 2 3)
[(list a b c) (+ a b c)])

Saída de exemplo:

6

Estruturas aninhadas:

(match '((1 2) (3 4))
[(list (list a b) (list c d)) (+ a b c d)])

Saída de exemplo:

10

Correspondência em uma "forma" de comando. Isso é genuinamente útil para analisar arquivos de configuração ou argumentos de linha de comando:

(match '(cp file1 file2)
[(list 'cp src dst) (printf "Copy ~a -> ~a\n" src dst)]
[_ "Unknown"])

Saída de exemplo:

Copy file1 -> file2

8. Múltiplos Valores de Retorno

Racket tem suporte nativo para uma função retornar mais de um valor, sem empacotá-los em uma tupla ou lista.

(define (divide x y)
(values (quotient x y) (remainder x y)))
(let-values ([(q r) (divide 20 3)])
(displayln q)
(displayln r))

Saída de exemplo:

6
2

9. Compreensão de Listas

As compreensões de lista do Python se parecem com [x*x for x in nums], onde nums é alguma lista de números. O equivalente do Racket é for/list:

(for/list ([x '(1 2 3 4)])
(* x x))

Saída de exemplo:

'(1 4 9 16)

Você pode adicionar uma cláusula de filtro usando #:when:

(for/list ([i (in-range 20)] #:when (even? i))
(* i i))

Saída de exemplo:

'(0 4 16 36 64 100 144 196 256 324)

10. Tabelas Hash

(define h (hash "cpu" 16 "ram" 32))
(hash-ref h "cpu")

Saída de exemplo:

16

Importante: (hash ...) cria uma tabela hash imutável. hash-set não altera h no lugar. Em vez disso, retorna uma tabela hash completamente nova:

(define h2 (hash-set h "disk" 1000))

h permanece o mesmo. h2 é a nova tabela. Se você quiser alterar uma tabela hash no lugar, use make-hash e hash-set!:

(define mh (make-hash))
(hash-set! mh "disk" 1000)

11. Estruturas (Structs)

Uma struct é um registro leve, imutável por padrão. É a resposta do Racket para classes simples.

(struct person (name age))
(define p (person "Senthil" 40))
(person-name p) ; "Senthil"
(person-age p)  ; 40

12. Contratos

Contratos permitem documentar e impor as expectativas de uma função em tempo de execução. São uma alternativa prática a um sistema de tipos estático separado, especialmente nos limites dos módulos.

(require racket/contract)
(define/contract (divide a b)
(-> number? (and/c number? (not/c zero?)) number?)
(/ a b))

Chamar (divide 5 0) levanta uma violação de contrato antes mesmo da divisão acontecer. A mensagem de erro mostra claramente qual argumento quebrou qual regra.

Saída de exemplo:

divide: contract violation
expected: (not/c zero?)
given: 0
in: an and/c case of
the 2nd argument of
(->
number?
(and/c number? (not/c zero?))
number?)
contract from: (function divide)
blaming: top-level
(assuming the contract is correct)
at: string:1:18
[,bt for context]

13. Macros: o Verdadeiro Superpoder do Racket

Uma função normal avalia seus argumentos antes de executar.

(define (twice x) (+ x x))

Uma macro é diferente. Ela transforma o código-fonte antes que esse código seja avaliado. As expressões de argumento nunca são avaliadas antecipadamente.

(define-syntax-rule (unless-again test body)
(if (not test) body (void)))
(unless-again (> 2 5) (displayln "Hello"))

Saída de exemplo:

Hello

Aqui está outro exemplo clássico. Ele executa uma expressão duas vezes, e a expressão permanece não avaliada até que a macro a expanda.

(define-syntax-rule (twice-run expr)
(begin expr expr))
(twice-run (displayln "Hello"))

Saída de exemplo:

Hello
Hello

É por isso que o Racket é bem conhecido nos círculos de linguagens de programação. Macros permitem estender a própria linguagem, não apenas chamar funções escritas nela.

14. Construindo DSLs

Imagine um arquivo de configuração que se parece com isto:

(server
port 8080
ssl yes
workers 8)

Essa sintaxe não existe no Racket por padrão. Você pode construí-la usando define-syntax-rule, ou as formas mais gerais syntax-rules e syntax-parse. Aqui está um pequeno exemplo:

(define-syntax-rule (repeat n body)
(for ([i n]) body))
(repeat 5 (displayln "Ostechnix"))

Saída de exemplo:

Ostechnix
Ostechnix
Ostechnix
Ostechnix
Ostechnix

Isso se lê como um recurso de linguagem embutido, porque no Racket você pode genuinamente adicionar novos recursos de linguagem. Poucas linguagens mainstream tornam isso tão fácil ou agradável.

15. Avaliação Preguiçosa e Streams

Racket é estrito (também chamado de eager) por padrão. Isso significa que normalmente calcula valores imediatamente. Mas o Racket também tem suporte embutido para preguiça através de racket/stream.

(require racket/stream)
(define nums (stream 1 2 3 4 5))
(stream-first nums)

Saída de exemplo:

1

Streams podem ser infinitos, porque seus elementos só são calculados quando você realmente os solicita:

(define ones (stream-cons 1 ones))
(stream-ref ones 1000000)

Saída de exemplo:

1

Apenas a parte do stream que você realmente solicita é calculada.

16. Scripting de Sistemas no Linux

Racket também pode atuar como uma linguagem muito capaz para scripts de shell e processamento de texto.

Lendo arquivos:

(file->string "/proc/cpuinfo")

ou linha por linha:

(with-input-from-file "/proc/meminfo"
(lambda ()
(displayln (read-line))))

Executando comandos do shell:

(system "ls -lh")

Capturando a saída em vez de imprimi-la:

(with-output-to-string
(lambda () (system "uname -r")))

Um pequeno filtro /proc/meminfo:

#lang racket
(define text (file->string "/proc/meminfo"))
(for ([line (string-split text "\n")])
(when (regexp-match? #rx"Mem" line)
(displayln line)))

Isso imprime as linhas correspondentes completas, por exemplo:

MemTotal:       11719360 kB
MemFree:         6641244 kB
MemAvailable:    9034448 kB

(Os números exatos dependem da sua máquina. Essas funções correspondem e imprimem linhas inteiras, não apenas rótulos.)

Programas de linha de comando:

#lang racket
(command-line #:args (name)
(printf "Hello ~a\n" name))

Execute assim:

racket hello.rkt Racket

Saída de exemplo:

Hello Racket

Um pipeline funcional. Isso lê um arquivo de números, remove linhas em branco, converte o texto em números e soma todos:

(define total
(foldl +
0
(map string->number
(filter (lambda (s) (not (string=? s "")))
(string-split (file->string "numbers.txt") "\n")))))

Quando map, filter e foldl se tornam naturais, pipelines como este se leem quase como uma descrição simples da tarefa, em vez de um conjunto de etapas de implementação.

Um pequeno servidor web. O Racket vem com uma biblioteca completa de aplicações web:

#lang racket
(require web-server/servlet
web-server/servlet-env)
(define (my-app req)
(response/xexpr '(html (body "Hello from Racket"))))
(serve/servlet my-app #:servlet-regexp #rx"")

Um manipulador de servidor deve retornar um valor de resposta adequado, não uma string simples, então esta versão envolve o texto usando response/xexpr. A opção #:servlet-regexp #rx"" também é necessária para que o servidor responda no endereço simples abaixo, em vez de apenas no caminho padrão.

Copie o código acima em um arquivo, por exemplo http.rkt, e execute-o:

$ racket http.rkt
Your Web application is running at http://localhost:8000/servlets/standalone.rkt.
Stop this program at any time to terminate the Web Server.

Agora, você verá o servidor web rodando em http://localhost:8000/. Um servidor web funcional em poucas linhas.

Pressione CTRL+C para parar o servidor web.

17. cond (Condicionais de Múltiplos Ramos)

if só lida com dois ramos. cond lida com muitos, e é o que o código Racket real usa em vez de if aninhados.

(define (sign n)
(cond
[(> n 0) "positive"]
[(< n 0) "negative"]
[else "zero"]))
(sign -5)

Saída de exemplo:

"negative"

Cada linha é verificada em ordem. O primeiro teste verdadeiro vence, e else captura todo o resto.

18. let e let*

let cria variáveis locais. Todos os valores são calculados primeiro, então uma variável não pode usar outra.

(let ([x 1] [y 2])
(+ x y))

Saída de exemplo:

3

let* permite que cada variável use as definidas antes dela.

(let* ([x 1] [y (+ x 1)])
(+ x y))

Saída de exemplo:

3

Escrever (y (+ x 1)) dentro de um let simples falharia, porque x ainda não está pronto.

19. Vetores

Listas são melhores para adicionar itens um de cada vez pela frente. Vetores são melhores quando você precisa de acesso rápido indexado, e podem ser alterados no lugar.

(define v (vector 1 2 3))
(vector-ref v 0)

Saída de exemplo:

1
(vector-set! v 0 99)
(vector-ref v 0)

Saída de exemplo:

99

20. Tratamento Básico de Erros

error levanta uma exceção. with-handlers a captura, para que seu programa possa responder em vez de quebrar.

(with-handlers ([exn:fail? (lambda (e) (displayln (exn-message e)))])
(error "Something went wrong"))

Saída de exemplo:

Something went wrong

Conclusão

Você viu agora as ideias centrais que moldam como o código Racket é escrito: expressões e funções de primeira classe, map/filter/fold, recursão, pattern matching, macros, contratos e scripting Linux suficiente para escrever ferramentas reais.

Nada disso faz de você um especialista. Dá a você um mapa funcional da linguagem, para que o Racket Guide e o Racket Reference façam mais sentido quando você os ler em seguida. A partir daqui, a melhor maneira de aprender mais é escrever pequenos programas e consultar as coisas conforme necessário.

Boa sorte!