Cheatsheet Clojure
Lisp moderno na JVM: funcional, imutável e concorrente
Clojure
Básico
Formas e S-expressions
; Tudo é uma lista em notação prefixa: (+ 1 2 3) ; 6 (* 2 3 4) ; 24 (str "Olá" " " "Mundo") ; "Olá Mundo" ; A primeira posição é a função: (- 10 3) ; 7 (/ 22 7) ; 22/7 (ratio!)
O Clojure usa S-expressions: código entre parênteses em notação prefixa, onde o primeiro elemento é a função e os restantes são os argumentos.
cond e case
; cond: múltiplas condições (cond (neg? x) "negativo" (zero? x) "zero" :else "positivo") ; case: valor constante (rápido) (case dia 1 "segunda" 2 "terca" "outro") ; default
O cond testa vários predicados em sequência, com :else como padrão. O case compara contra valores constantes e é mais rápido, mas exige um ramo default.
Keywords e symbols
; Keywords (auto-avaliadas, usadas como chaves):
:nome
:idade
(:nome {:nome "Ana"}) ; "Ana"
; Keywords com namespace:
::local ; :meu.ns/local
:user/nome
; Symbols (referem-se a vars):
'minha-funcao
+ ; o símbolo da função +
(quote x) ; equivale a 'xUm keyword (começa com :) avalia-se a si próprio e é muito usado como chave de mapa. Um symbol refere-se a uma var; o quote (') impede a sua avaliação.
Tipos de dados
42 ; integer (Long) 3.14 ; float (Double) 22/7 ; ratio (racional exato!) "texto" ; string \a ; char true false ; boolean nil ; null :keyword ; keyword 'simbolo ; symbol
Os tipos principais incluem Long, Double, Ratio (racionais exatos), String, Char, Boolean, nil, keywords e symbols. Todos são imutáveis.
Operações aritméticas
(+ 1 2) ; 3 (- 10 3) ; 7 (* 4 5) ; 20 (/ 22 7) ; 22/7 (ratio) (double (/ 22 7)) ; 3.142... (mod 10 3) ; 1 (resto) (inc 5) ; 6 (dec 5) ; 4 (quot 10 3) ; 3 (quociente)
Os operadores são funções prefixas: + - * /. A divisão inteira devolve um Ratio exato; usa double para obter decimal. inc/dec somam/subtraem 1.
do e comentários
; do executa várias formas em sequência: (do (println "passo 1") (println "passo 2") (+ 1 2)) ; retorna 3 (última forma) ; Comentários: ; isto é um comentário de linha #_ (forma ignorada pelo reader) (+ 1 #_ 2 3) ; 4 (o 2 é ignorado)
O do executa várias expressões e devolve o valor da última. Comentários usam ;; o macro #_ faz o reader ignorar completamente a forma seguinte.
def e let
; def cria um binding global (var):
(def nome "Clojure")
(def pi 3.14159)
; let cria bindings locais:
(let [x 10
y 20]
(+ x y)) ; 30
; let com reutilização:
(let [base 5
dobro (* base 2)]
(+ base dobro)) ; 15O def define uma variável global (uma var). O let cria bindings locais visíveis só dentro do seu corpo — é a forma preferida para valores temporários.
Comparação e lógicos
; Comparação (encadeável!): (= 1 1) ; true (< 1 2 3) ; true (1<2<3) (>= 5 5 3) ; true ; Lógicos: (and true true) ; true (or false true) ; true (not true) ; false ; Predicados úteis: (nil? nil) ; true (even? 4) ; true
As comparações (= < > <= >=) aceitam vários argumentos encadeados. Os lógicos são and, or e not. Funções terminadas em ? são predicados que devolvem booleanos.
if e when
; if: (if teste então senão) (if (> x 0) "positivo" "negativo ou zero") ; when: só o ramo verdadeiro (when (pos? x) (println "É positivo!")) ; if retorna valor (é expressão): (def sinal (if (neg? x) -1 1))
O if é uma expressão que devolve um valor: (if teste então senão). O when executa o corpo só se o teste for verdadeiro e devolve nil caso contrário.
Strings
; Concatenar: (str "Olá" " " "Mundo") ; "Olá Mundo" ; Biblioteca clojure.string: (require '[clojure.string :as str]) (str/upper-case "olá") ; "OLÁ" (str/lower-case "OLÁ") ; "olá" (str/trim " oi ") ; "oi" (str/split "a,b,c" #",") ; ["a" "b" "c"] (str/join "-" [1 2 3]) ; "1-2-3"
A função str concatena e converte valores em texto. O namespace clojure.string oferece operações como upper-case, trim, split e join.
Funções
defn
; Definir função: (defn somar [a b] (+ a b)) (somar 3 5) ; 8 ; Com docstring: (defn saudacao "Retorna uma saudação" [nome] (str "Olá, " nome "!")) (saudacao "Ana") ; "Olá, Ana!"
O defn define uma função nomeada. A docstring opcional documenta-a e é visível com (doc saudacao) no REPL.
Shorthand #()
; #() é açúcar para fn: #(* % 2) ; (fn [x] (* x 2)) #(+ %1 %2) ; dois argumentos #(apply + %&) ; argumentos variádicos ; Uso prático: (map #(* % 2) [1 2 3]) ; (2 4 6) (filter #(> % 3) [1 2 3 4 5]) ; (4 5) ; Atenção: não aninhar #()!
A sintaxe #() é uma forma curta de função anónima. % é o argumento, %1/%2 para vários e %& para o resto. Não deve ser aninhada.
Threading macros
; -> insere como 1.º argumento:
(-> " olá "
(clojure.string/trim)
(clojure.string/upper-case)) ; "OLÁ"
; ->> insere como último argumento:
(->> (range 10)
(filter even?)
(map #(* % %))
(reduce +)) ; 120
; some-> para nil-safe:
(some-> obj :campo :subcampo)As threading macros evitam parênteses aninhados. O -> passa o resultado como primeiro argumento de cada forma; o ->> como último. O some-> para se encontrar nil.
Arity overloading
; Várias aridades (n.º de args):
(defn area
([r] (* Math/PI r r)) ; círculo
([l a] (* l a))) ; retângulo
(area 2) ; 12.56...
(area 3 4) ; 12
; Aridade com recursão:
(defn somar-ate
([n] (somar-ate n 0))
([n acc]
(if (zero? n) acc
(recur (dec n) (+ acc n)))))Uma função pode ter várias aridades (números de argumentos), cada uma com o seu corpo. É comum uma aridade curta delegar noutra mais completa com um valor inicial.
partial e comp
; partial: aplica argumentos iniciais (def dobro (partial * 2)) (dobro 5) ; 10 (def saudar (partial str "Olá, ")) (saudar "Ana") ; "Olá, Ana" ; comp: compõe funções (direita->esquerda) (def transformar (comp inc #(* % 2))) (transformar 3) ; 7 (inc (2*3))
O partial fixa os primeiros argumentos de uma função, criando uma nova. O comp compõe funções da direita para a esquerda: (comp f g) é f(g(x)).
Funções variádicas
; & recolhe o resto dos argumentos: (defn soma-todos [& nums] (apply + nums)) (soma-todos 1 2 3 4) ; 10 ; Misturar fixos e variádicos: (defn log [nivel & msgs] (println nivel (apply str msgs))) (log "INFO" "a" "b") ; INFO ab
O & recolhe os argumentos restantes numa sequência. Permite funções com número variável de argumentos, como soma-todos que aceita qualquer quantidade.
apply
; apply chama função com uma seq de args: (apply + [1 2 3]) ; 6 (apply max [3 7 2]) ; 7 ; Args fixos + coleção: (apply str "a" ["b" "c"]) ; "abc" ; Sem apply vs com apply: (+ [1 2 3]) ; ERRO (apply + [1 2 3]) ; 6
O apply chama uma função passando os elementos de uma coleção como argumentos individuais. É essencial para usar funções como + ou max com uma lista.
Funções anónimas (fn)
; fn cria função anónima: (fn [x] (* x 2)) ; Uso direto: ((fn [x] (* x 2)) 5) ; 10 ; Como argumento: (map (fn [x] (* x x)) [1 2 3]) ; (1 4 9) ; Com nome local (recursão): ((fn fat [n] (if (<= n 1) 1 (* n (fat (dec n))))) 5)
O fn cria uma função anónima. Pode ter um nome interno (para recursão) e é útil quando a função é usada uma só vez, por exemplo em map ou filter.
loop e recur
; loop/recur: recursão em tail-call
(defn fatorial [n]
(loop [i n
acc 1]
(if (<= i 1)
acc
(recur (dec i) (* acc i)))))
(fatorial 10) ; 3628800O loop define pontos de recursão e o recur salta de volta sem acumular stack (tail-call optimization). É a forma idiomática de fazer ciclos no Clojure.
Coleções
Vectors
; Vector: ordenado, acesso por índice (def v [1 2 3 4 5]) (nth v 0) ; 1 (v 2) ; 3 (como função) (count v) ; 5 (conj v 6) ; [1 2 3 4 5 6] (assoc v 0 99) ; [99 2 3 4 5] (get v 10 :default) ; :default
Um vector ([]) é uma coleção ordenada com acesso eficiente por índice. O conj acrescenta no fim e o assoc substitui uma posição, devolvendo sempre um novo vector.
Sets
; Set: coleção sem duplicados
(def s #{1 2 3 4 5})
(contains? s 3) ; true
(s 3) ; 3 (ou nil)
(conj s 6) ; #{1 2 3 4 5 6}
(disj s 3) ; #{1 2 4 5}
; Operações (clojure.set):
(require '[clojure.set :as set])
(set/union #{1 2} #{2 3}) ; #{1 2 3}
(set/intersection #{1 2} #{2 3}) ; #{2}Um set (#{}) guarda valores únicos sem ordem. O conj adiciona, o disj remove e contains? testa pertença. O namespace clojure.set dá union e intersection.
Coleções e sequências
; seq converte qualquer coleção em sequência:
(seq [1 2 3]) ; (1 2 3)
(seq {:a 1}) ; ([:a 1])
(seq "abc") ; (\a \b \c)
; Testar vazio (idiomático):
(if (seq colecao)
"tem dados"
"vazio")
; (empty? coll) também funcionaQualquer coleção pode ser vista como uma seq (sequência) com seq. É idiomático testar (seq coll) para verificar se tem dados, pois devolve nil se vazia.
Lists
; List: acesso eficiente no início (def l '(1 2 3)) (first l) ; 1 (rest l) ; (2 3) (cons 0 l) ; (0 1 2 3) (conj l 0) ; (0 1 2 3) (no início!) ; Listas são lazy-friendly: (map inc l) ; (2 3 4)
Uma list ('()) é otimizada para aceder e acrescentar no início. O conj adiciona à frente (ao contrário do vector). São ideais para sequências lazy.
Destructuring de vectors
; Extrair posições: (let [[a b c] [1 2 3]] (+ a b c)) ; 6 ; Primeiro e resto: (let [[x & resto] [1 2 3 4]] [x resto]) ; [1 (2 3 4)] ; Ignorar posições: (let [[_ segundo _] [10 20 30]] segundo) ; 20
O destructuring de vectors extrai valores por posição dentro de um let ou parâmetro. O & recolhe o resto e o _ ignora posições não usadas.
Maps
; Map: pares chave-valor
(def pessoa {:nome "Ana" :idade 30})
; Acesso:
(:nome pessoa) ; "Ana"
(get pessoa :idade) ; 30
(get pessoa :email "N/A") ; "N/A"
; keywords como função:
(:idade pessoa) ; 30Um map ({}) guarda pares chave-valor. As keywords funcionam como funções de acesso: (:nome pessoa). O get aceita um valor default se a chave não existir.
Destructuring de maps
; :keys extrai chaves keyword:
(let [{:keys [nome idade]} pessoa]
(str nome " - " idade))
; Com default e rename:
(let [{:keys [nome]
:or {nome "Anónimo"}} dados]
nome)
; Em parâmetros de função:
(defn mostrar [{:keys [nome idade]}]
(println nome idade))O destructuring de maps com :keys extrai valores de chaves keyword para variáveis locais. O :or define defaults e funciona também em parâmetros de funções.
Atualizar maps
(def pessoa {:nome "Ana" :idade 30})
; assoc: adiciona/substitui chave
(assoc pessoa :email "ana@mail.com")
; update: aplica função ao valor
(update pessoa :idade inc) ; idade 31
; dissoc: remove chave
(dissoc pessoa :idade)
; merge: combina maps
(merge pessoa {:cidade "Lisboa"})Os maps são imutáveis: assoc adiciona ou substitui, update aplica uma função ao valor de uma chave, dissoc remove e merge combina. Todos devolvem um novo map.
Estruturas aninhadas
(def dados
{:user {:nome "Ana"
:morada {:cidade "Porto"}}})
; get-in: acede a caminho aninhado
(get-in dados [:user :morada :cidade])
; "Porto"
; assoc-in: atualiza caminho
(assoc-in dados [:user :idade] 30)
; update-in: aplica função no caminho
(update-in dados [:user :nome] clojure.string/upper-case)Para estruturas aninhadas, get-in lê um caminho de chaves, assoc-in define um valor nesse caminho e update-in aplica-lhe uma função — tudo de forma imutável.
Sequências
map, filter, reduce
; map: transforma cada elemento (map inc [1 2 3]) ; (2 3 4) (map #(* % %) [1 2 3]) ; (1 4 9) ; filter: mantém os que passam (filter even? (range 10)) ; (0 2 4 6 8) ; reduce: agrega num valor (reduce + [1 2 3 4]) ; 10 (reduce + 100 [1 2 3]) ; 106 (valor inicial)
O map aplica uma função a cada elemento, o filter mantém os que satisfazem o predicado e o reduce combina tudo num único valor com um acumulador.
for comprehension
; Combina vários geradores:
(for [x (range 3)
y (range 3)]
[x y])
; Com filtro :when:
(for [x (range 10)
:when (even? x)]
(* x x)) ; (0 4 16 36 64)
; Com binding :let:
(for [x (range 5)
:let [y (* x 2)]]
[x y])O for é uma list comprehension que combina geradores. O :when filtra elementos e o :let cria bindings intermédios. Devolve uma sequência lazy.
some, every?, not-any?
; some: primeiro valor verdadeiro
(some even? [1 3 4 5]) ; true
(some #{3} [1 2 3]) ; 3
; every?: todos passam?
(every? pos? [1 2 3]) ; true
(every? even? [2 4 5]) ; false
; not-any?: nenhum passa?
(not-any? neg? [1 2 3]) ; true
; not-empty: nil se vazia
(not-empty []) ; nilO some devolve o primeiro valor verdadeiro do predicado (ou nil). O every? testa se todos passam e o not-any? se nenhum passa. São predicados de sequência.
take, drop, first, rest
(take 3 [1 2 3 4 5]) ; (1 2 3) (drop 2 [1 2 3 4 5]) ; (3 4 5) (take-while pos? [3 2 1 0 -1]) ; (3 2 1) (drop-while pos? [3 2 1 0 -1]) ; (0 -1) (first [1 2 3]) ; 1 (last [1 2 3]) ; 3 (rest [1 2 3]) ; (2 3) (nth [1 2 3] 1) ; 2
Estas funções fatiam sequências: take/drop retiram N elementos, take-while/drop-while usam um predicado. first, last e rest acedem a partes.
Transducers
; Compor transformações sem coleções intermédias:
(def xform
(comp
(filter even?)
(map #(* % %))
(take 5)))
; Aplicar de várias formas:
(into [] xform (range 100)) ; [0 4 16 36 64]
(transduce xform + (range 100)) ; 56
(sequence xform (range 100))Um transducer é uma transformação reutilizável e independente da coleção. Composto com comp, aplica-se com into, transduce ou sequence sem criar coleções intermédias.
Lazy sequences
; range é lazy (avalia sob pedido):
(def nums (range)) ; infinita!
(take 5 nums) ; (0 1 2 3 4)
; lazy-seq cria seq preguiçosa:
(defn fib-seq
([] (fib-seq 0 1))
([a b]
(lazy-seq
(cons a (fib-seq b (+ a b))))))
(take 10 (fib-seq))
; (0 1 1 2 3 5 8 13 21 34)As lazy sequences só calculam os elementos quando necessários. O lazy-seq cria sequências preguiçosas, permitindo estruturas infinitas consumidas com take.
concat, interleave, partition
; concat: junta sequências (concat [1 2] [3 4]) ; (1 2 3 4) ; interleave: alterna elementos (interleave [:a :b :c] [1 2 3]) ; (:a 1 :b 2 :c 3) ; partition: agrupa em blocos (partition 2 [1 2 3 4 5]) ; ((1 2) (3 4)) (partition-all 2 [1 2 3 4 5]) ; ((1 2) (3 4) (5)) ; flatten: remove aninhamento (flatten [[1 2] [3 [4]]]) ; (1 2 3 4)
O concat junta sequências, o interleave alterna elementos de várias e o partition agrupa em blocos de N. O flatten remove todos os níveis de aninhamento.
range, repeat, cycle
; range: sequência numérica
(range 5) ; (0 1 2 3 4)
(range 2 10) ; (2 3 ... 9)
(range 0 10 2) ; (0 2 4 6 8) (passo)
; repeat: repete um valor
(take 4 (repeat "x")) ; ("x" "x" "x" "x")
; cycle: repete a coleção
(take 6 (cycle [1 2 3])) ; (1 2 3 1 2 3)
; repeatedly: chama uma função
(take 3 (repeatedly rand))O range gera sequências numéricas (com início, fim e passo). O repeat repete um valor, o cycle repete uma coleção e o repeatedly chama uma função várias vezes.
sort, group-by, frequencies
; sort / sort-by:
(sort [3 1 2]) ; (1 2 3)
(sort-by :idade pessoas) ; ordena por chave
; group-by: agrupa por função
(group-by even? [1 2 3 4])
; {false (1 3), true (2 4)}
; frequencies: conta ocorrências
(frequencies [:a :b :a :c :a])
; {:a 3, :b 1, :c 1}
; distinct: remove duplicados
(distinct [1 1 2 3 3]) ; (1 2 3)O sort ordena e o sort-by ordena por uma função ou chave. O group-by agrupa elementos num map, o frequencies conta ocorrências e o distinct remove duplicados.
Concorrência
Atoms
; Estado mutável atómico e síncrono: (def contador (atom 0)) ; Ler (deref ou @): @contador ; 0 (deref contador) ; 0 ; Atualizar com swap!: (swap! contador inc) ; 1 (swap! contador + 10) ; 11 ; Reset (substitui): (reset! contador 0)
Um atom guarda estado mutável de forma atómica e síncrona. Lê-se com @ ou deref; atualiza-se com swap! (aplica função) ou reset! (substitui).
alter e commute
(def conta (ref 100)) ; alter: aplica função (ordem importa) (dosync (alter conta - 30)) ; commute: para operações comutativas ; (mais rápido, sem ordem garantida) (dosync (commute conta + 50)) ; ref-set: substitui o valor (dosync (ref-set conta 0))
Dentro de dosync, o alter aplica uma função mantendo a ordem, enquanto o commute é otimizado para operações comutativas (como +). O ref-set substitui o valor.
swap! e validação
; swap! aplica função de forma atómica: (def saldo (atom 100)) (swap! saldo - 30) ; 70 ; Com argumentos extra: (swap! saldo + 50) ; 120 ; Validator (rejeita valores inválidos): (def idade (atom 0 :validator pos?)) (swap! idade inc) ; 1 (ok) (reset! idade -5) ; ERRO (falha validator)
O swap! aplica uma função ao valor do atom de forma atómica, reintentando em caso de conflito. Um :validator rejeita atualizações que devolvam valores inválidos.
Agents
; Atualização assíncrona em fila: (def logger (agent [])) ; send enfileira uma ação: (send logger conj "Evento 1") (send logger conj "Evento 2") ; Ler (pode estar desatualizado): @logger ; Esperar conclusão: (await logger) ; send-off para ações bloqueantes (IO)
Um agent gere estado atualizado de forma assíncrona. O send enfileira ações processadas uma de cada vez; o await espera a conclusão. Usa send-off para operações de IO.
Watchers
; add-watch observa mudanças:
(def contador (atom 0))
(add-watch contador :log
(fn [key ref antigo novo]
(println antigo "->" novo)))
(swap! contador inc) ; imprime: 0 -> 1
(swap! contador inc) ; imprime: 1 -> 2
; Remover:
(remove-watch contador :log)Um watcher é uma função chamada sempre que um atom (ou ref/agent) muda. Recebe a chave, a referência, o valor antigo e o novo. Remove-se com remove-watch.
Futures
; future: cálculo assíncrono (def resultado (future (Thread/sleep 2000) (* 42 42))) ; Fazer outra coisa entretanto... ; @ bloqueia até ter o valor: @resultado ; 1764 ; deref com timeout: (deref resultado 5000 :timeout) (realized? resultado) ; true
Um future executa um cálculo noutra thread e devolve de imediato. O @ (deref) bloqueia até o valor estar pronto. O deref com timeout devolve um valor se expirar.
Refs (STM)
; Refs: estado coordenado transacional (def conta-a (ref 1000)) (def conta-b (ref 0)) ; Transação com dosync: (dosync (alter conta-a - 100) (alter conta-b + 100)) @conta-a ; 900 @conta-b ; 100
As refs permitem atualizar vários valores de forma coordenada via Software Transactional Memory. Dentro de dosync, as mudanças são atómicas — ou todas acontecem ou nenhuma.
Promises
; promise: valor entregue uma vez (def p (promise)) ; Numa thread, entregar: (deliver p "valor") ; Noutra, esperar: @p ; "valor" ; realized? testa se já foi entregue: (realized? p) ; true ; Útil para sincronizar threads
Uma promise é um canal de sentido único: entrega-se um valor uma única vez com deliver e quem faz deref espera até ele existir. Serve para sincronizar threads.
Avançado
Macros
; Macro: código que gera código
(defmacro unless [cond & body]
`(if (not ~cond)
(do ~@body)))
(unless false
(println "Executa!"))
; Ver a expansão:
(macroexpand '(unless true (println "x")))Um macro transforma código em tempo de compilação, permitindo criar novas formas de sintaxe. O macroexpand mostra o código gerado, útil para depurar macros.
Multimethods
; defmulti: dispatch por função
(defmulti area :tipo)
(defmethod area :circulo [{:keys [raio]}]
(* Math/PI raio raio))
(defmethod area :retangulo [{:keys [l a]}]
(* l a))
(area {:tipo :circulo :raio 2}) ; 12.56
(area {:tipo :retangulo :l 3 :a 4}) ; 12Um multimethod faz dispatch dinâmico com base no resultado de uma função. O defmulti define a função de dispatch e cada defmethod trata um valor específico.
Syntax quote
; ` = syntax quote (com namespace) ; ~ = unquote (avalia expressão) ; ~@ = unquote-splicing (insere seq) `(if (not ~cond) (do ~@body)) ; Exemplo: (let [x 5] `(+ ~x 1)) ; (clojure.core/+ 5 1) ; ~@ espalha uma lista: (let [args '(1 2 3)] `(+ ~@args)) ; (+ 1 2 3)
No syntax quote, ` cria um template de código, ~ (unquote) insere o valor de uma expressão e ~@ (unquote-splicing) espalha uma sequência de argumentos.
reify
; reify: cria objeto anónimo com protocolos
(def descritor
(reify Descritivel
(descrever [this] "objeto anónimo")))
(descrever descritor) ; "objeto anónimo"
; Implementar interfaces Java:
(reify Comparable
(compareTo [this outro] 0))O reify cria um objeto anónimo que implementa protocols ou interfaces Java, sem definir um tipo nomeado. É útil para implementações pontuais e rápidas.
Protocols
; Definir um protocolo (interface): (defprotocol Descritivel (descrever [this])) ; Implementar para tipos: (extend-protocol Descritivel String (descrever [s] (str "String: " s)) Long (descrever [n] (str "Número: " n))) (descrever "olá") ; "String: olá" (descrever 42) ; "Número: 42"
Um protocol define um conjunto de funções (como uma interface). O extend-protocol implementa-o para tipos existentes, permitindo polimorfismo por tipo de forma eficiente.
Vars e dynamic binding
; ^:dynamic permite re-binding temporário: (def ^:dynamic *impressora* :ecra) (defn log [msg] (println *impressora* msg)) ; binding altera só dentro do bloco: (binding [*impressora* :ficheiro] (log "guardado")) ; :ficheiro guardado (log "normal") ; :ecra normal
Uma var marcada ^:dynamic pode ser re-atribuída temporariamente com binding, visível só dentro do bloco. Por convenção, o nome fica entre asteriscos.
defrecord
; Record: tipo de dados nomeado
(defrecord Pessoa [nome idade])
; Criar:
(def p (->Pessoa "Ana" 30))
(map->Pessoa {:nome "Rui" :idade 25})
; Acesso (como map):
(:nome p) ; "Ana"
; Record + protocolo:
(defrecord Animal [nome]
Descritivel
(descrever [a] (str "Animal: " (:nome a))))Um defrecord cria um tipo de dados com campos nomeados, acessível como um map. Os construtores ->Nome e map->Nome são gerados automaticamente, e pode implementar protocols.
Exceções
; try/catch/finally:
(try
(/ 1 0)
(catch ArithmeticException e
(println "Erro:" (.getMessage e)))
(finally
(println "sempre executa")))
; Lançar exceção:
(throw (ex-info "inválido" {:codigo 400}))
; try+ (slingshot) lida com mapasO try envolve código que pode falhar, o catch trata a exceção por tipo e o finally executa sempre. O throw lança exceções; ex-info cria exceções com dados.
IO e Java Interop
Chamar métodos Java
; .método chama método de instância: (.toUpperCase "olá") ; "OLÁ" (.length "texto") ; 5 (.replace "a-b" "-" "+") ; "a+b" ; Classe/método estático: (Math/abs -42) ; 42 (Math/sqrt 16) ; 4.0 (System/currentTimeMillis) ; Campo estático: Math/PI ; 3.14159...
A interop com Java usa .método para métodos de instância e Classe/método para estáticos. Os campos estáticos acedem-se com Classe/CAMPO, como Math/PI.
Escrever ficheiros (writer)
(require '[clojure.java.io :as io])
; Escrever com writer:
(with-open [w (io/writer "saida.txt")]
(.write w "primeira linha\n")
(.write w "segunda linha\n"))
; Acrescentar:
(with-open [w (io/writer "log.txt"
:append true)]
(.write w "nova entrada\n"))O io/writer cria um writer para escrita controlada. O with-open fecha-o automaticamente no fim. A opção :append true acrescenta ao ficheiro existente.
Criar objetos e import
; Ponto final cria instância: (java.util.Date.) (java.util.ArrayList.) ; Com argumentos: (java.util.ArrayList. 10) ; Import para nomes curtos: (import '[java.time LocalDate]) (LocalDate/now) ; doto: configura objeto com métodos (doto (java.util.HashMap.) (.put "a" 1) (.put "b" 2))
O ponto final (Classe.) cria uma instância. O import permite usar nomes curtos. O macro doto chama vários métodos no mesmo objeto, útil para configurar objetos Java mutáveis.
Entrada e saída padrão
; Imprimir:
(println "Olá" "Mundo") ; com newline
(print "sem newline")
(prn {:a 1}) ; formato legível (edn)
; Ler do stdin:
(def linha (read-line))
(println "Disseste:" linha)
; printf formatado:
(printf "Nome: %s, Idade: %d%n" "Ana" 30)O println imprime com quebra de linha e o print sem. O prn imprime em formato edn (legível pelo Clojure). O read-line lê uma linha do stdin.
slurp e spit
; slurp: lê ficheiro inteiro para string (def conteudo (slurp "dados.txt")) ; spit: escreve string num ficheiro (spit "saida.txt" "Olá Mundo") ; Acrescentar (append): (spit "log.txt" "linha\n" :append true) ; Também funciona com URLs: ; (slurp "https://exemplo.com")
O slurp lê todo o conteúdo de um ficheiro para uma String e o spit escreve uma String num ficheiro. A opção :append true acrescenta em vez de sobrescrever.
Namespaces e require
; Declarar namespace:
(ns meu-app.core
(:require [clojure.string :as str]
[clojure.set :as set]
[meu-app.utils :refer [ajuda]]))
; Usar:
(str/upper-case "olá") ; com alias
(set/union #{1} #{2})
(ajuda) ; refer direto
; No REPL:
(require '[clojure.string :as str])O ns declara o namespace e as dependências. O :require carrega namespaces, :as define um alias e :refer importa funções específicas para uso direto.
Ler ficheiros (reader)
(require '[clojure.java.io :as io])
; Ler linha a linha:
(with-open [r (io/reader "dados.txt")]
(doseq [linha (line-seq r)]
(println linha)))
; Ler tudo como seq de linhas:
(with-open [r (io/reader "dados.txt")]
(doall (line-seq r)))O namespace clojure.java.io dá acesso a readers e writers. O with-open garante o fecho do recurso; o line-seq devolve uma sequência lazy de linhas.
Ferramentas
Clojure CLI e deps.edn
# Comandos principais:
clojure -M -m meu-app.core ; correr
clojure -A:dev ; com alias
clojure -T:build uber ; build
# deps.edn define dependências:
{:deps
{org.clojure/clojure
{:mvn/version "1.11.1"}
ring/ring-core
{:mvn/version "1.10.0"}}}O Clojure CLI usa o ficheiro deps.edn para declarar dependências e aliases. O comando clojure -M executa o programa; os aliases ativam configurações extra.
Testes
(ns meu-app.core-test
(:require [clojure.test :refer
[deftest is testing]]
[meu-app.core :refer [somar]]))
(deftest teste-somar
(testing "soma básica"
(is (= 5 (somar 2 3)))
(is (= 0 (somar -1 1)))))
; Correr: clojure -M:test (ou lein test)Os testes usam clojure.test: deftest define um teste, is verifica uma asserção e testing agrupa com uma descrição. Correm-se com lein test ou via CLI.
Leiningen
# Criar projeto: lein new app meu-projeto # Comandos principais: lein run ; executar lein repl ; REPL lein test ; testes lein uberjar ; JAR executável # project.clj define dependências: ; :dependencies ; [[org.clojure/clojure "1.11.1"]]
O Leiningen é uma ferramenta de build clássica. O lein new cria projetos, o lein run executa e o lein uberjar gera um JAR autónomo. As dependências ficam no project.clj.
Boas práticas
; Prefere dados imutáveis e funções puras:
(defn total [itens]
(reduce + (map :preco itens)))
; Usa keywords como funções de acesso:
(map :nome pessoas)
; Threading para pipelines legíveis:
(->> dados
(filter :ativo)
(map :valor)
(reduce +))
; Nomes de predicados terminam em ?:
(defn valido? [x] (pos? x))Privilegia dados imutáveis, funções puras e keywords como acessores. Usa threading macros para pipelines legíveis e termina nomes de predicados em ? e funções de mutação em !.
REPL
# Iniciar: clojure ; ou: lein repl ; Comandos úteis no REPL: (doc map) ; documentação (source map) ; código fonte (find-doc "lazy"); procurar docs (apropos "map") ; símbolos com "map" ; Recarregar namespace: (require 'meu-app.core :reload)
O REPL é o ambiente interativo do Clojure. Funções como doc, source, find-doc e apropos ajudam a explorar a biblioteca padrão em tempo real.
Estrutura de projeto
meu-projeto/
deps.edn ; dependências
src/
meu_app/
core.clj ; ns meu-app.core
utils.clj ; ns meu-app.utils
test/
meu_app/
core_test.clj
resources/ ; ficheiros estáticos
; _ no ficheiro = - no namespace:
; meu_app/core.clj -> meu-app.coreUm projeto organiza o código em src/ (com namespaces) e test/. O caminho do ficheiro define o namespace: underscores (_) no ficheiro viram hífens (-) no nome.