DevTools

Cheatsheet Clojure

Lisp moderno na JVM: funcional, imutável e concorrente

Voltar às linguagens
Clojure
66 cards encontrados
Categorias:
Versões:

Básico


10 cards
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 'x

Um 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)) ; 15

O 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


9 cards
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)   ; 3628800

O 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


9 cards
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.setunion 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 funciona

Qualquer 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)         ; 30

Um 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


9 cards
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 [])            ; nil

O 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


8 cards
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


8 cards
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}) ; 12

Um 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 mapas

O 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


7 cards
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


6 cards
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.core

Um 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.