mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-03 19:45:38 +03:00
571 lines
22 KiB
Markdown
571 lines
22 KiB
Markdown
---
|
|
language: clojure
|
|
filename: learnclojure-pt.clj
|
|
contributors:
|
|
- ["Adam Bard", "http://adambard.com/"]
|
|
translators:
|
|
- ["Mariane Siqueira Machado", "https://twitter.com/mariane_sm"]
|
|
- ["Ygor Sad", "https://github.com/ysads"]
|
|
lang: pt-br
|
|
---
|
|
|
|
Clojure é uma linguagem da família do Lisp desenvolvida para a JVM (máquina virtual Java). Possui uma ênfase muito mais forte em [programação funcional] (https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_funcional) pura do que Common Lisp, mas inclui diversos recursos [STM](https://en.wikipedia.org/wiki/Software_transactional_memory) para lidar com estado e mutabilidade, caso isso seja necessário.
|
|
|
|
Essa combinação permite gerenciar processamento concorrente de maneira muito simples - frequentemente, de modo automático.
|
|
|
|
(Sua versão de clojure precisa ser pelo menos 1.2)
|
|
|
|
|
|
```clojure
|
|
; Comentários começam por ponto e vírgula
|
|
|
|
; Código Clojure é escrito em formas - 'forms', em inglês. Tais estruturas são
|
|
; simplesmente listas de valores encapsuladas dentro de parênteses, separados por
|
|
; espaços em branco.
|
|
|
|
; Ao interpretar um código em Clojure, o interpretador ou leitor - do inglês 'reader' - assume
|
|
; que o primeiro valor dentro de uma forma é uma função ou macro, de modo que os demais valores
|
|
; são seus argumentos. Isso se deve ao fato de que Clojure, por ser uma derivação de Lisp,
|
|
; usa notação prefixa (ou polonesa).
|
|
|
|
; Num arquivo, a primeira chamada deve ser sempre para a função ns,
|
|
; que é responsável por definir em qual namespace o código em questão
|
|
; deve ser alocado
|
|
(ns learnclojure)
|
|
|
|
; Alguns exemplos básicos:
|
|
|
|
; Aqui, str é uma função e "Olá" " " e "Mundo" são seus argumentos. O que ela faz é criar
|
|
; uma string concatenando seus argumentos.
|
|
(str "Olá" " " "Mundo") ; => "Olá Mundo"
|
|
|
|
; Note que espaços em branco separam os argumentos de uma função. Opcionalmente vírgulas
|
|
; podem ser usadas, se você quiser.
|
|
(str, "Olá", " ", "Mundo") ; => "Olá Mundo"
|
|
|
|
; As operações matemáticas básicas usam os operadores de sempre
|
|
(+ 1 1) ; => 2
|
|
(- 2 1) ; => 1
|
|
(* 1 2) ; => 2
|
|
(/ 2 1) ; => 2
|
|
|
|
; Esses operadores aceitam um número arbitrário de argumentos
|
|
(+ 2 2 2) ; = 2 + 2 + 2 => 6
|
|
(- 5 1 1) ; = 5 - 1 - 1 => 3
|
|
(* 3 3 3 3) ; = 3 * 3 * 3 * 3 => 81
|
|
|
|
; Para verificar se dois valores são iguais, o operador = pode ser usado
|
|
(= 1 1) ; => true
|
|
(= 2 1) ; => false
|
|
|
|
; Para saber se dois valores são diferentes
|
|
(not= 1 2) ; => true
|
|
(not (= 1 2)) ; => true
|
|
|
|
; Conforme vimos acima, é possível aninhar duas formas
|
|
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2
|
|
(* (- 3 2) (+ 1 2)) ; = (3 - 2) * (1 + 2) => 3
|
|
|
|
; Se a leitura ficar comprometida, as fórmulas também podem ser escritas em múltiplas linhas
|
|
(* (- 3 2)
|
|
(+ 1 2)) ; => 3
|
|
(*
|
|
(- 3 2)
|
|
(+ 1 2)) ; => 3
|
|
|
|
|
|
; Tipos
|
|
;;;;;;;;;;;;;
|
|
|
|
; Por ter interoperabilidade com Java, Clojure usa os tipos de objetos de Java para booleanos,
|
|
; strings e números. Para descobrir qual o tipo de um valor, você pode usar a função `class`:
|
|
(class 1234) ; Literais Integer são java.lang.Long por padrão
|
|
(class 1.50) ; Literais Float são java.lang.Double
|
|
(class "oi") ; Strings sempre usam aspas duplas e são java.lang.String
|
|
(class false) ; Booleanos são java.lang.Boolean
|
|
|
|
; Tenha cuidado, ao dividir valores inteiros:
|
|
(= (/ 1 2)
|
|
(/ 1.0 2.0)) ; => false
|
|
|
|
(class (/ 1 2)) ; => clojure.lang.Ratio
|
|
(class (/ 1.0 2.0)) ; => java.lang.Double
|
|
|
|
; Aqui temos uma diferença em relação a Java, pois valores nulos são representados por `nil`
|
|
(class nil) ; nil
|
|
|
|
|
|
; Coleções e sequências
|
|
;;;;;;;;;;;;;;;;;;;
|
|
|
|
; Os dois tipos básicos de coleção são listas - "list" em inglês - e vetores - "vectors"
|
|
; no original. A principal diferença entre eles se
|
|
; dá pela implementação:
|
|
; - Vetores são implementados como arrays
|
|
; - Listas são listas ligadas
|
|
(class [1 2 3]) ; => clojure.lang.PersistentVector
|
|
(class '(1 2 3)) ; => clojure.lang.PersistentList
|
|
|
|
; Outra forma de declarar listas é usando a função list
|
|
(list 1 2 3) ; => '(1 2 3)
|
|
|
|
; Clojure classifica conjuntos de dados de duas maneiras
|
|
|
|
; "Coleções" são grupos simples de dados
|
|
; Tanto listas quanto vetores são coleções:
|
|
(coll? '(1 2 3)) ; => true
|
|
(coll? [1 2 3]) ; => true
|
|
|
|
; "Sequências" (seqs) são descrições abstratas de listas de dados.
|
|
; Sequências - ou seqs - são conjuntos de dados com avaliação "lazy"
|
|
; Apenas listas são seqs:
|
|
(seq? '(1 2 3)) ; => true
|
|
(seq? [1 2 3]) ; => false
|
|
|
|
; Ter avaliação lazy significa que uma seq somente precisa prover uma informação quando
|
|
; ela for requisitada. Isso permite às seqs representar listas infinitas.
|
|
(range) ; => (0 1 2 3 4 ...)
|
|
(cycle [1 2]) ; => (1 2 1 2 1 2 ...)
|
|
(take 4 (range)) ; => (0 1 2 3)
|
|
|
|
; A função cons é usada para adicionar um item ao início de uma lista ou vetor:
|
|
(cons 4 [1 2 3]) ; => (4 1 2 3)
|
|
(cons 4 '(1 2 3)) ; => (4 1 2 3)
|
|
|
|
; Já conj adiciona um item em uma coleção sempre do jeito mais eficiente.
|
|
; Em listas, isso significa inserir no início. Já em vetores, ao final.
|
|
(conj [1 2 3] 4) ; => [1 2 3 4]
|
|
(conj '(1 2 3) 4) ; => (4 1 2 3)
|
|
|
|
; Concatenação de coleções pode ser feita usando concat. Note que ela sempre gera uma
|
|
; seq como resultado e está sujeita a problemas de perfomance em coleções grandes, por
|
|
; conta da natureza lazy das seqs.
|
|
(concat '(1 2) [3 4]) ; => (1 2 3 4)
|
|
(concat [1 2] '(3 4)) ; => (1 2 3 4)
|
|
|
|
; Outra forma de concatenar coleções é usando into. Ela não está sujeita a problemas
|
|
; com a avaliação lazy, mas o resultado final da ordem e do tipo dos argumentos passados
|
|
(into [1 2] '(3 4)) ; => [1 2 3 4]
|
|
(into '(1 2) [3 4]) ; => (4 3 1 2)
|
|
|
|
; Note que em into a ordem dos parâmetros influencia a coleção final.
|
|
(into [1 2] '(3 4)) ; => (1 2 3 4)
|
|
(into '(1 2) [3 4]) ; => (4 3 1 2)
|
|
|
|
; As funções filter e map podem ser usadas para interagir com as coleções. Repare que
|
|
; elas sempre retornam seqs, independentemente do tipo do seu argumento.
|
|
(map inc [1 2 3]) ; => (2 3 4)
|
|
(filter even? [1 2 3 4]) ; => (2 4)
|
|
|
|
; Use reduce reduzir coleções a um único valor. Também é possível passar um argumento
|
|
; para o valor inicial das operações
|
|
(reduce + [1 2 3]) ; = (+ (+ (+ 1 2) 3) 4) => 10
|
|
(reduce + 10 [1 2 3 4]) ; = (+ (+ (+ (+ 10 1) 2) 3) 4) => 20
|
|
(reduce conj [] '(3 2 1)) ; = (conj (conj (conj [] 3) 2) 1) => [3 2 1]
|
|
|
|
; Reparou na semelhança entre listas e as chamadas de código Clojure? Isso se deve ao
|
|
; fato de que todo código clojure é escrito usando listas. É por isso que elas sempre
|
|
; são declaradas com o caracter ' na frente. Dessa forma o interpretador não tenta
|
|
; avaliá-las.
|
|
'(+ 2 3) ; cria uma lista com os elementos +, 2 e 3
|
|
(+ 2 3) ; o interpretador chama a função + passando como argumentos 2 e 3
|
|
|
|
; Note que ' é apenas uma abreviação para a função quote.
|
|
(quote (1 2 3)) ; => '(1 2 3)
|
|
|
|
; É possível passar uma lista para que o interpretador a avalie. Note que isso está
|
|
; sujeito ao primeiro elemento da lista ser um literal com um nome de uma função válida.
|
|
(eval '(+ 2 3)) ; => 5
|
|
(eval '(1 2 3)) ; dá erro pois o interpretador tenta chamar a função 1, que não existe
|
|
|
|
|
|
; Funções
|
|
;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
; Use fn para criar novas funções. Uma função sempre retorna sua última expressão.
|
|
(fn [] "Olá Mundo") ; => fn
|
|
|
|
; Para executar suas funções, é preciso chamá-las, envolvendo-as em parênteses.
|
|
((fn [] "Olá Mundo")) ; => "Olá Mundo"
|
|
|
|
; Como isso não é muito prático, você pode nomear funções atribuindo elas a literais.
|
|
; Isso torna muito mais fácil chamá-las:
|
|
(def ola-mundo (fn [] "Olá Mundo")) ; => fn
|
|
(ola-mundo) ; => "Olá Mundo"
|
|
|
|
; Você pode abreviar esse processo usando defn:
|
|
(defn ola-mundo [] "Olá Mundo")
|
|
|
|
; Uma função pode receber uma lista de argumentos:
|
|
(defn ola
|
|
[nome]
|
|
(str "Olá " nome))
|
|
(ola "Jonas") ; => "Olá Jonas"
|
|
|
|
; É possível criar funções que recebam multivariadas, isto é, que aceitam números
|
|
; diferentes de argumentos:
|
|
(defn soma
|
|
([] 0)
|
|
([a] a)
|
|
([a b] (+ a b)))
|
|
|
|
(soma) ; => 0
|
|
(soma 1) ; => 1
|
|
(soma 1 2) ; => 3
|
|
|
|
; Funções podem agrupar argumentos extras em uma seq:
|
|
(defn conta-args
|
|
[& args]
|
|
(str "Você passou " (count args) " argumentos: " args))
|
|
(conta-args 1 2 3 4) ; => "Você passou 4 argumentos: (1 2 3 4)"
|
|
|
|
; Você pode misturar argumentos regulares e argumentos em seq:
|
|
(defn ola-e-conta
|
|
[nome & args]
|
|
(str "Olá " nome ", você passou " (count args) " argumentos extras"))
|
|
(ola-e-conta "Maria" 1 2 3 4) ; => "Olá Maria, você passou 4 argumentos extras"
|
|
|
|
|
|
; Nos exemplos acima usamos def para associar nomes a funções, mas poderíamos usá-lo
|
|
; para associar nomes a quaisquer valores:
|
|
(def xis :x)
|
|
xis ; => :x
|
|
|
|
; Inclusive, tais literais podem possuir alguns caracteres não usuais em outras linguagens:
|
|
(def *num-resposta* 42)
|
|
(def conexao-ativa? true)
|
|
(def grito-de-medo! "AAAAAAA")
|
|
(def ->vector-vazio [])
|
|
|
|
; É possível, inclusive, criar apelidos a nomes que já existem:
|
|
(def somar! soma)
|
|
(somar! 41 1) ; => 42
|
|
|
|
; Uma forma rápida de criar funções é por meio de funções anônimas. Elas são ótimas
|
|
; para manipulação de coleções e seqs, já que podem ser passadas para map, filter
|
|
; e reduce. Nessas funções, % é substituído por cada um dos items na seq ou na coleção:
|
|
(filter #(not= % nil) ["Joaquim" nil "Maria" nil "Antônio"]) ; => ("Joaquim" "Maria" "Antônio")
|
|
(map #(* % (+ % 2)) [1 2]) ; => (3 8)
|
|
|
|
|
|
; Mapas
|
|
;;;;;;;;;;
|
|
|
|
; Existem dois tipos de mapas: hash maps e array maps. Ambos compartilham uma mesma
|
|
; interface e funções. Hash maps são mais rápidos para retornar dados, mas não mantém
|
|
; as chaves ordenadas.
|
|
(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap
|
|
(class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap
|
|
|
|
; Clojure converte automaticamente array maps em hash maps, por meio da maioria das
|
|
; funções de manipulação de mapas, caso eles fiquem grandes o suficiente. Não é
|
|
; preciso se preocupar com isso.
|
|
|
|
; Chaves podem ser qualquer valor do qual possa ser obtido um hash, mas normalmente
|
|
; usam-se keywords como chave, por possuírem algumas vantagens.
|
|
(class :a) ; => clojure.lang.Keyword
|
|
|
|
; Keywords são como strings, porém, duas keywords de mesmo valor são sempre armazenadas
|
|
; na mesma posição de memória, o que as torna mais eficientes.
|
|
(identical? :a :a) ; => true
|
|
(identical? (String. "a") (String. "a")) ; => false
|
|
|
|
(def mapa-strings {"a" 1 "b" 2 "c" 3})
|
|
mapa-strings ; => {"a" 1, "b" 2, "c" 3}
|
|
|
|
(def mapa-keywords {:a 1 :b 2 :c 3})
|
|
mapa-keywords ; => {:a 1, :c 3, :b 2}
|
|
|
|
; Você pode usar um mapa como função para recuperar um valor dele:
|
|
(mapa-strings "a") ; => 1
|
|
(mapa-keywords :a) ; => 1
|
|
|
|
; Se a chave buscada for uma keyword, ela também pode ser usada como função para recuperar
|
|
; valores. Note que isso não funciona com strings.
|
|
(:b mapa-keywords) ; => 2
|
|
("b" mapa-strings) ; => java.lang.String cannot be cast to clojure.lang.IFn
|
|
|
|
; Se você buscar uma chave que não existe, Clojure retorna nil:
|
|
(mapa-strings "d") ; => nil
|
|
|
|
; Use assoc para adicionar novas chaves em um mapa.
|
|
(def mapa-keywords-estendido (assoc mapa-keywords :d 4))
|
|
mapa-keywords-estendido ; => {:a 1, :b 2, :c 3, :d 4}
|
|
|
|
; Mas lembre-se que tipos em Clojure são sempre imutáveis! Isso significa que o mapa
|
|
; inicial continua com as mesmas informações e um novo mapa, com mais dados, é criado
|
|
; a partir dele
|
|
mapa-keywords ; => {:a 1, :b 2, :c 3}
|
|
|
|
; assoc também pode ser usado para atualizar chaves:
|
|
(def outro-mapa-keywords (assoc mapa-keywords :a 0))
|
|
outro-mapa-keywords ; => {:a 0, :b 2, :c 3}
|
|
|
|
; Use dissoc para remover chaves
|
|
(dissoc mapa-keywords :a :b) ; => {:c 3}
|
|
|
|
; Mapas também são coleções - mas não seqs!
|
|
(coll? mapa-keywords) ; => true
|
|
(seq? mapa-keywords) ; => false
|
|
|
|
; É possível usar filter, map e qualquer outra função de coleções em mapas.
|
|
; Porém a cada iteração um vetor no formato [chave valor] vai ser passado como
|
|
; argumento. Por isso é conveniente usar funções anônimas.
|
|
(filter #(odd? (second %)) mapa-keywords) ; => ([:a 1] [:c 3])
|
|
(map #(inc (second %)) mapa-keywords) ; => (2 3 4)
|
|
|
|
; Conjuntos
|
|
;;;;;;
|
|
|
|
; Conjuntos são um tipo especial de coleções que não permitem elementos repetidos.
|
|
; Eles podem ser criados com #{} ou com a função set.
|
|
(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}
|
|
(class #{1 2 3}) ; => clojure.lang.PersistentHashSet
|
|
|
|
; Note que nem sempre um set vai armazenar seus elementos na ordem esperada.
|
|
(def meu-conjunto #{1 2 3})
|
|
meu-conjunto ; => #{1 3 2}
|
|
|
|
; Adição funciona normalmente com conj.
|
|
(conj meu-conjunto 4) ; => #{1 4 3 2}
|
|
|
|
; Remoção, no entanto, precisa ser feita com disj:
|
|
(disj meu-conjunto 1) ; => #{3 2}
|
|
|
|
; Para saber se um elemento está em um conjunto, use-o como função. Nesse aspecto
|
|
; conjuntos funcionam de maneira semelhante a mapas.
|
|
(meu-conjunto 1) ; => 1
|
|
(meu-conjunto 4) ; => nil
|
|
|
|
|
|
; Condicionais e blocos
|
|
;;;;;;;;;;;;;;;;;
|
|
|
|
; Você pode usar um bloco let para criar um escopo local, no qual estarão disponíveis
|
|
; os nomes que você definir:
|
|
(let [a 1 b 2]
|
|
(+ a b)) ; => 3
|
|
|
|
(let [cores {:yellow "Amarelo" :blue "Azul"}
|
|
nova-cor :red
|
|
nome-cor "Vermelho"]
|
|
(assoc cores nova-cor nome-cor)) ; => {:yellow "Amarelo", :blue "Azul", :red "Vermelho"}
|
|
|
|
; Formas do tipo if aceitam três argumentos: a condição de teste, o comando a ser
|
|
; executado caso a condição seja positiva; e o comando para o caso de ela ser falsa.
|
|
(if true "a" "b") ; => "a"
|
|
(if false "a" "b") ; => "b"
|
|
|
|
; Opcionalmente você pode não passar o último argumento, mas se a condição for falsa
|
|
; o if vai retornar nil.
|
|
(if false "a") ; => nil
|
|
|
|
; A forma if somente aceita um comando para ser executado em cada caso. Se você
|
|
; precisar executar mais comandos, você pode usar a função do:
|
|
(if true
|
|
(do
|
|
(print "Olá ")
|
|
(print "Mundo"))) ; => escreve "Olá Mundo" na saída
|
|
|
|
; Se você só deseja tratar o caso de sua condição ser verdadeira, o comando when é
|
|
; uma alternativa melhor. Seu comportamento é idêntico a um if sem condição negativa.
|
|
; Uma de suas vantagens é permitir a execução de vários comandos sem exigir do:
|
|
(when true "a") ; => "a"
|
|
(when true
|
|
(print "Olá ")
|
|
(print "Mundo")) ; => também escreve "Olá Mundo" na saída
|
|
|
|
; Isso ocorre porque when possui um bloco do implícito. O mesmo se aplica a funções e
|
|
; comandos let:
|
|
(defn escreve-e-diz-xis
|
|
[nome]
|
|
(print "Diga xis, " nome)
|
|
(str "Olá " nome))
|
|
(escreve-e-diz-xis "João") ;=> "Olá João", além de escrever "Diga xis, João" na saída.
|
|
|
|
(let [nome "Nara"]
|
|
(print "Diga xis, " nome)
|
|
(str "Olá " nome)) ;=> "Olá João", além de escrever "Diga xis, João" na saída.
|
|
|
|
|
|
; Módulos
|
|
;;;;;;;;;;;;;;;
|
|
|
|
; Você pode usar a função use para carregar todas as funções de um módulo.
|
|
(use 'clojure.set)
|
|
|
|
; Agora nós podemos usar operações de conjuntos definidas nesse módulo:
|
|
(intersection #{1 2 3} #{2 3 4}) ; => #{2 3}
|
|
(difference #{1 2 3} #{2 3 4}) ; => #{1}
|
|
|
|
; Isso porém não é uma boa prática pois dificulta saber de qual módulo cada função
|
|
; veio, além de expor o código a conflitos de nomes, caso dois módulos diferentes
|
|
; definam funções com o mesmo nome. A melhor forma de referenciar módulos é por meio
|
|
; de require:
|
|
(require 'clojure.string)
|
|
|
|
; Com isso podemos chamar as funções de clojure.string usando o operador /
|
|
; Aqui, o módulo é clojure.string e a função é blank?
|
|
(clojure.string/blank? "") ; => true
|
|
|
|
; Porém isso não é muito prático, por isso é possível dar para um nome mais curto para
|
|
; o módulo ao carregá-lo:
|
|
(require '[clojure.string :as str])
|
|
(str/replace "alguém quer teste?" #"[aeiou]" str/upper-case) ; => "AlgUém qUEr tEstE?"
|
|
|
|
; Nesse exemplo usamos também a construção #"", que delimita uma expressão regular.
|
|
|
|
; É possível carregar outros módulos direto na definição do namespace. Note que nesse
|
|
; contexto não é preciso usar ' antes do vetor que define a importação do módulo.
|
|
(ns test
|
|
(:require
|
|
[clojure.string :as str]
|
|
[clojure.set :as set]))
|
|
|
|
|
|
; Operadores thread
|
|
;;;;;;;;;;;;;;;;;
|
|
|
|
; Uma das funções mais interessantes de clojure são os operadores -> e ->> - respectivamente
|
|
; thread-first e thread-last macros. Elas permitem o encadeamento de chamadas de funções,
|
|
; sendo perfeitas para melhorar a legibilidade em transformações de dados.
|
|
|
|
; -> usa o resultado de uma chamada como o primeiro argumento da chamada à função seguinte:
|
|
(-> " uMa StRIng com! aLG_uNs ##problemas. "
|
|
(str/replace #"[!#_]" "")
|
|
(str/replace #"\s+" " ")
|
|
str/trim ; se a função só aceitar um argumento, não é preciso usar parênteses
|
|
(str/lower-case)) ; => "uma string com alguns problemas."
|
|
|
|
; Na thread uma string com vários problemas foi passada como primeiro argumento à função
|
|
; str/replace, que criou uma nova string, a partir da original, porém somente com caracteres
|
|
; alfabéticos. Essa nova string foi passada como primeiro argumento para a chamada str/replace
|
|
; seguinte, que criou uma nova string sem espaços duplos. Essa nova string foi então passada
|
|
; como primeiro argumento para str/trim, que removeu espaços de seu início e fim, passando essa
|
|
; última string para str/lower-case, que a converteu para caracteres em caixa baixa.
|
|
|
|
; ->> é equivalente a ->, porém o retorno de cada função é passado como último argumento da
|
|
; função seguinte. Isso é particularmente útil para lidar com seqs, já que as funções que
|
|
; as manipulam sempre as tomam como último argumento.
|
|
(->> '(1 2 3 4)
|
|
(filter even?) ; => '(2 4)
|
|
(map inc) ; => '(3 5)
|
|
(reduce *)) ; => 15
|
|
|
|
|
|
; Java
|
|
;;;;;;;;;;;;;;;;;
|
|
|
|
; A biblioteca padrão de Java é enorme e possui inúmeros algoritmos e estruturas de
|
|
; dados já implementados. Por isso é bastante conveniente saber como usá-la dentro
|
|
; de Clojure.
|
|
|
|
; Use import para carregar um módulo Java.
|
|
(import java.util.Date)
|
|
|
|
; Você pode importar classes Java dentro de ns também:
|
|
(ns test
|
|
(:import java.util.Date
|
|
java.util.Calendar
|
|
java.util.ArrayList))
|
|
|
|
; Use o nome da clase com um "." no final para criar uma nova instância
|
|
(def instante (Date.))
|
|
(class instante) => ; java.util.Date
|
|
|
|
; Para chamar um método, use o operador . com o nome do método. Outra forma é
|
|
; usar simplesmente .<nome do método>
|
|
(. instante getTime) ; => retorna um inteiro representando o instante
|
|
(.getTime instante) ; => exatamente o mesmo que acima
|
|
|
|
; Para chamar métodos estáticos dentro de classes Java, use /
|
|
(System/currentTimeMillis) ; => retorna um timestamp
|
|
|
|
; Note que não é preciso importar o módulo System, pois ele está sempre presente
|
|
|
|
; Caso queira submeter uma instância de uma classe mutável a uma sequência de operações,
|
|
; você pode usar a função doto. Ela é funciona de maneira semelhante à função -> - ou
|
|
; thread-first -, exceto pelo fato de que ele opera com valores mutáveis.
|
|
(doto (java.util.ArrayList.)
|
|
(.add 11)
|
|
(.add 3)
|
|
(.add 7)
|
|
(java.util.Collections/sort)) ; => #<ArrayList [3, 7, 11]>
|
|
|
|
|
|
; STM
|
|
;;;;;;;;;;;;;;;;;
|
|
|
|
; Até aqui usamos def para associar nomes a valores. Isso, no entanto, possui algumas
|
|
; limitações, já que, uma vez definido essa associação, não podemos alterar o valor
|
|
; para o qual um nome aponta. Isso significa que nomes definidos com def não se
|
|
; comportam como as variáveis de outras linguagens.
|
|
|
|
; Para lidar com estado persistente e mutação de valores, Clojure usa o mecanismo Software
|
|
; Transactional Memory. O atom é o mais simples de todos. Passe pra ele um valor inicial e
|
|
; e ele criará um objeto que é seguro de atualizar:
|
|
(def atom-mapa (atom {}))
|
|
|
|
; Para acessar o valor de um atom, você pode usar a função deref ou o operador @:
|
|
@atom-mapa ; => {}
|
|
(deref atom-mapa) ; => {}
|
|
|
|
; Para mudar o valor de um atom, você deve usar a função swap!
|
|
; O que ela faz é chamar a função passada usando o atom como seu primeiro argumento. Com
|
|
; isso, ela altera o valor do atom de maneira segura.
|
|
(swap! atom-mapa assoc :a 1) ; Atribui a atom-mapa o resultado de (assoc {} :a 1)
|
|
(swap! atom-mapa assoc :b 2) ; Atribui a atom-mapa o resultado de (assoc {:a 1} :b 2)
|
|
|
|
; Observe que essas chamadas alteraram de fato o valor de atom-mapa. Seu novo valor é:
|
|
@atom-mapa ; => {:a 1 :b 2}
|
|
|
|
; Isso é diferente de fazer:
|
|
(def atom-mapa-2 (atom {}))
|
|
(def atom-mapa-3 (assoc @atom-mapa-2 :a 1))
|
|
|
|
; Nesse exemplo, atom-mapa-2 permanece com o seu valor original e é gerado um novo mapa,
|
|
; atom-mapa-3, que contém o valor de atom-mapa-2 atualizado. Note que atom-mapa-3 é um
|
|
; simples mapa, e não uma instância de um atom
|
|
@atom-mapa-2 ; => {}
|
|
atom-mapa-3 ; => {:a 1}
|
|
|
|
(class atom-mapa-2) ; => clojure.lang.Atom
|
|
(class atom-mapa-3) ; => clojure.lang.PersistentArrayMap
|
|
|
|
; A ideia é que o valor do atom só será atualizado se, após ser executada a função passada
|
|
; para swap!, o atom ainda estiver com o mesmo valor de antes. Isto é, se durante a execução
|
|
; da função alguém alterar o valor do atom, swap! reexecutará a função recebida usando o valor
|
|
; atual do átoma como argumento.
|
|
|
|
; Isso é ótimo em situações nas quais é preciso garantir a consistência de algum valor - tais
|
|
; como sistemas bancários e sites de compra. Para mais exemplos e informações sobre outras
|
|
; construções STM:
|
|
|
|
; Exemplos e aplicações: https://www.braveclojure.com/zombie-metaphysics/
|
|
; Refs: http://clojure.org/refs
|
|
; Agents: http://clojure.org/agents
|
|
```
|
|
|
|
### Leitura adicional
|
|
|
|
Esse tutorial está longe de ser completo, mas deve ser suficiente para que você possa dar seus primeiros passos em Clojure.
|
|
Caso queira aprender mais:
|
|
|
|
* clojure.org tem vários artigos:
|
|
[http://clojure.org/](http://clojure.org/)
|
|
|
|
* Brave Clojure possui um e-book que explora em profundidade diversos recursos de clojure, incluindo ótimos exemplos:
|
|
[https://www.braveclojure.com/](https://www.braveclojure.com/)
|
|
|
|
* clojuredocs.org tem documentação com exemplos para quase todas as funções principais (pertecentes ao core):
|
|
[http://clojuredocs.org/quickref/Clojure%20Core](http://clojuredocs.org/quickref/Clojure%20Core)
|
|
|
|
* 4clojure possui alguns problemas e desafios interessantes para quem quiser treinar clojure ou programação funcional:
|
|
[https://4clojure.oxal.org/](https://4clojure.oxal.org/)
|
|
|
|
* clojure-doc.org tem um bom número de artigos para iniciantes:
|
|
[http://clojure-doc.org/](http://clojure-doc.org/)
|
|
|
|
Clojure for the Brave and True é um livro de introdução ao Clojure e possui uma versão gratuita online:
|
|
[https://www.braveclojure.com/clojure-for-the-brave-and-true/](https://www.braveclojure.com/clojure-for-the-brave-and-true/)
|