Boas vindas a mais um curso de Clojure. Neste curso discutiremos bastante aspectos de programação funcional aplicado às coleções. Já vimos como esses elementos se mixam.
Implementaremos nosso próprio map
e uma variação de um reduce
, de maneira simplificada, mas ainda sim implementações nossas que utilizarão recurso de calda e loop. Aprenderemos também em como lidar com os problemas que esses recursos podem apresentar eventualmente no código.
Aprenderemos diversas outras funções que surgirão no nosso dia a dia durante o trabalho com coleções, gerar dados e assim por diante. Será que tudo que pedimos em Clojure será executado de maneira imperativa? Ou será que simplesmente descrevemos o que deve ser feito e isso é executado no momento mais conveniente?
Há um pouco dos dois casos e cada uma com sua vantagem, e enquanto pessoas que programam, ora isso será uma preocupação para nós, ora não. Teremos essa discussão ao longo do curso, e espero que seja proveitosa para você!
Neste curso, começaremos um projeto novo de Clojure do Leiningen, cujo título será "loja". Esse processo demora alguns minutos.
Termos o diretório "src", então "loja" que por sua vez abrigará o documento core.clj
:
(ns loja.core)
(defn foo
"I don't do a whole lot."
[x]
(println x "Hello, World!"))
Primeiramente, executaremos o nosso Leiningen. Com o botão direito clicaremos sobre o arquivo project.clj
e selecionaremos a opção "Run 'REPL for loja'. Agora podemos trabalha em loja.core
.
Já conhecemos alguns tipos de coleções, por exemplo um vetor que pode ter vários elementos. Por exemplo um vetor que contém diversos nomes como :
;["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"]
Também conhecemos o modelo associativo, ou dicionário. Nesta coleção poderíamos dizer que guilherme
tem 37
anos e o paulo
tem 39
. O uso da vírgula para separar elementos no Clousure é opcional.
;["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"]
;{ "guilherme" 37, "paulo" 39}
Poderíamos ter ainda outro tipo de coleção que é meramente uma sequencia de elementos. Ainda poderia ver uma coleção que, ao invés da restrição ser por acesso randômico via array, seria uma lista ligada, em que se utiliza ;'()
.
;["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"]
;{ "guilherme" 37, "paulo" 39}
;'(1 2 3 4 5)
Outro tipo de coleção possível é aquela que contém 0 ou 1 elemento, totalmente válida. Um tipo de coleção também válida é aquela composta por um conjunto.
No cotidiano da programação, utilizamos coleções o tempo todo, por isso destacamos a importância de map
, reduce
e filter
. Quando queremos passar por vários elementos de uma coleção executando algo em cada um delas e ainda retornar o resultado da execução para cada um deles fazemos o map
. Quando queremos filtrar alguns dos elementos, utilizamos o filter
e quando queremos reduzir os elementosacionamos o reduce
.
E se quisermos usar um loop
como em outras linguagens? Ou mesmo for
? Existem recursos do gênero, mas antes aprenderemos de que maneira funcional podemos fazer algo para todos os elementos de um vetor. Por exemplo, num conjunto de nomes, gostaríamos de imprimir todos eles.
Sabemos que essa é uma ação possível:
(map println ["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"])
O map
realiza esse trabalho para nós. O map
é um código funcional que realiza algo, não se trata de uma key word da linguagem, trata-se de uma função que foi implementada por alguém, ainda que carregado de otimizações.
O map
passa pelo primeiro elemento e executa a função, ao final só resta println ["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"]
, e então depois println ["guilherme" "carlos" "paulo" "lucia" "ana"])
, até que todos os elementos tenham sido lidos, e resta apenas printl[]
Conseguimos também coletar o primeiro elemento de um vetor, e não existe apenas uma maneira de fazer isso. Uma maneira bem simples é usar o first
.
(println (first ["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"]))
Será coletado apenas daniela
. Existem maneiras de coletar todos os elementos, exceto o primeiro, como o rest
e o next
. Há pequenas diferenças entre os dois, principalmente quando estamos de uma sequência vazia de elementos, neste caso o rest
devolve vazio, já o next
nos devolve nulo. Então, o next
poderia ser utilizado para descobrir quando uma sequencia termina.
Já utilizamos várias vezes o termo "sequência" ao invés de "vetor". O uso de {
explicita um vetor, mas a verdade é que várias funções nos devolvem uma sequência de elementos, como esse sequência foi implementada são informações que não sabemos.
Podemos transformar um vetor vazio em uma sequência (seq
), e o seq
de um vetor vazio é nulo, mas o seq
de um vetor com elementos resulta em uma sequencia com esses elementos.
Queremos executar, usando o first
, rest
ou next
ser capaz de chamar o println
para todos os elementos, quer dizer queremos implementar uma versão simples do nosso mapa.
Começaremos com uma função e uma sequência. Feito isso coletaremos o primeiro elemento dessa sequência (first
)
(println "\n\n\n\nMEU MAPA")
(defn meu-mapa
[funcao sequencia]
(let [primeiro (first sequencia)]
(funcao primeiro)))
Vamos testar.
(meu-mapa println ["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"])
Funcionou perfeitamente, a foi impresso daniela
. Não queremos imprimir o primeiro elemento, mas todo o resto da sequência. Chamaremos meu-mapa
e então usaremos a função rest
.
(println "\n\n\n\nMEU MAPA")
(defn meu-mapa
[funcao sequencia]
(let [primeiro (first sequencia)]
(funcao primeiro)
(meu-mapa funcao(rest sequencia))
))
Ao testaremos nosso código, veremos que ele entrou em loop. Por mais que o loop não seja explicito, no código chamamos nós mesmos até que imprimimos ana
e e ficamos com uma sequencia vazia. O first de uma sequência vazia é nulo, e então ficamos nesse ciclo de loop. Portanto precisamos em algum momento parar o laço.
Estamos nos invocando recursivamente, sem parar. Precisamos interromper esse processo quando não houver mais próximo elemento a ser buscado. Realizaremos então um if
(defn meu-mapa
[funcao sequencia]
(let [primeiro (first sequencia)]
(if primeiro
(funcao primeiro)
(meu-mapa funcao(rest sequencia)))
))
(meu-mapa println ["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"])
Ao testarmos nosso código, veremos impresso daniela
, apenas. Por que isso aconteceu?
O if
possui duas funções if
e else
. Queremos escrever o if
com mais um bloco de código, e então inseriremos o do
(defn meu-mapa
[funcao sequencia]
(let [primeiro (first sequencia)]
(if primeiro
(do
(funcao primeiro)
(meu-mapa funcao(rest sequencia))))
))
(meu-mapa println ["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"])
Feito isso,teremos todos os outros nomes impressos. Isso se deu porque o if
verdadeiro executa a ação. Se o primeiro for nulo ou falso, a impressão é interrompida.
Imprimiremos o mapa com um vetor que contém false
.
(defn meu-mapa
[funcao sequencia]
(let [primeiro (first sequencia)]
(if primeiro
(do
(funcao primeiro)
(meu-mapa funcao(rest sequencia))))
))
(meu-mapa println ["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"])
(meu-mapa println ["daniela" false "carlos" "paulo" "lucia" "ana"])
Novamente é impressão é interrompinda após daniela
, isso ocorreu porque o primeiro elemento possui um valor falso. Logo, o if
não é o melhor recurso para usarmos nessa situação, pois não conseguimos verificar o valor.
Outra maneira de usar o if
é realizar a verificação de nulo.
(defn meu-mapa
[funcao sequencia]
(let [primeiro (first sequencia)]
(if (not(nil? primeiro))
(do
(funcao primeiro)
(meu-mapa funcao(rest sequencia))))))
(meu-mapa println ["daniela" "guilherme" "carlos" "paulo" "lucia" "ana"])
(meu-mapa println ["daniela" false "carlos" "paulo" "lucia" "ana"])
Teremos impresso na tela
daniela
guilherme
carlos
paulo
lucia
ana
daniela
false
carlos
paulo
lucia
ana
Nesse caso o mapa deu certo, inclusive se tentarmos imprimir um vetor vazio, nada será impresso, o mesmo o ocorrerá para vetores nulos. Essa é uma maneira de trabalharmos, mas não a única. Por meio da recursão, conseguimos implementar tarefas que deverão ser executadas mais de uma vez.
Aprendemos a implementar o map, e a partir disso conseguimos fazer praticamente qualquer ação que envolta implementar coleções. Contudo, devemos tomar cuidado: imagine que ao invés da sequencia de nomes, teremos um range
de vários números.
(meu-mapa println (range1000))
Nesse processo é impresso o número e a função é evocada. Isso começa a empilhar as chamadas da função, e isso pode acabar gerando problemas de sintaxe. No caso dessa função, o erro foi StackOverflowError. Se for apenas uma recursão pura, uma hora a memória do computador terá problemas. Se utilizarmos somente uma recursão pura a memória falha em algum momento, afinal os computadores são finitos e a execução precisa armazenar na memória as variáveis locais para poder chamar a próxima invocação.
A recursão é quando chamamos "nós mesmos", isso é, evocamos a própria função. Dito isso, o compilador de Clojure para Java otimiza a recursão e a transforma em um laço, que não possui o problema da "pilha de execução", pois a variável vai mudando de valor.
Podemos então utilizar o termo recur
, lembrando que essa deve ser a última coisa a ser feita logo antes de retornar a função, ou no momento do else
.
(defn meu-mapa
[funcao sequencia]
(let [primeiro (first sequencia)]
(if (not(nil? primeiro))
(do
(funcao primeiro)
(recur funcao (rest sequencia))))))
Em tempo de compilação,o Clojure irá transformar o recur
em um laço. Dessa maneira, não teremos mais problemas ao executar um range1000
.
Ao observarmos como um todo, a desvantagem do laço tradicional utilizado em outras linguagens, é que ele articulava diversas referências de símbolos e que possuem valores diferentes de acordo com o momento do laço. Existem casos complexos quando se trata do uso dessa função. Contudo, neste caso, o uso foi funcional, com preservação de memória. Chamamos meu-mapa
com uma função e temos uma sequência neste instante.
O curso Clojure: coleções no dia a dia possui 122 minutos de vídeos, em um total de 33 atividades. Gostou? Conheça nossos outros cursos de Clojure em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.