Por que Clojure?!

©2023
Computação @ UFCG

Slides da aula

Estudar uma nova linguagem de programação sempre requer esforço cognitivo significativo. E, em geral, é siginificativo o suficiente para servir de entrave para o aprendizado. É que o cérebro gasta bastante energia para aprender uma nova linguagem e essa energia poderia ser direcionado pra outras coisas mais produtivas e interessantes. E é exatamente o que ocorre conosco, se não tivermos motivação o suficiente para aprender a nova linguagem. É por isso, que começo tentando responder e deixar claro pra mim o que espero obter desse processo de aprendizado.

Faz sentido estudarmos Clojure?!

Esta pergunta é perfeitamente válida porque Clojure, nem de longe, está entre as linguagens mais populares da atualidade. Pelo ranking de popularidade de linguagens da Tiobe, Clojure está longe de ser listada entre as Top 10. De fato, não está nem nas Top 50 (felizmente, aparece nas Top 100!).

E o motivo pode não ser só a popularidade. Uma leitura rápida sobre Clojure e você descobre que Clojure difere significativamente da maioria das linguagens modernas por descender de Lisp, uma linguagem antiga (criada em 1958!) com sintaxe estranha e foco em programação funcional.

Como se não bastasse isso, não há nenhum outro grupo da UFCG (ao mens que eu conheça) que use Clojure. E isso significa que não há apoio da comunidade local. A cultura local privilegia Java e Python. E com essas duas linguagens, por que precisaríamos de algo mais?

Ser Lisp é um bom motivo em si!

A verdade, contudo, é que ser baseada em lisp é um bom motivo por si só. Talvez, o principal deles, se você pensar em termos de sua formação em ciência da computação.

Lisp foi criada em 1958 por John McCarthy quando ainda estava no MIT (pouco depois se mudou para Stanford onde continuou o desenvolvimento de Lisp). O objetivo inicial de McCarthy era criar uma linguagem apropriada para a IA (termo que, por sinal, se atribui a ele desde o famoso workshop de verão de 1956). Para atender a esse propósito, acreditava ele, a linguagem deveria ser capaz de processamento simbólico mais que de processamento numérico. A base pra isso seria o uso de listas como forma de representação (ele se inspirou em outra linguagem chamada IPL).

Lisp é, certamente, uma das 10 linguagens mais influentes de toda a história da programação e das linguagens de programação. Há autores que a colocam no que chamam das 4 linguagens mãe, junto com Fortran, Cobol e Algol. Tanto Python, como Java, C, C++, C#, Ruby e demais linguagens tipicamente estudadas mais amplamente são derivadas principalmente de Algol e Fortran, no que se refere a sintaxe e semântica. Mas, uma quantidade significativa de características tanto de Algol como de Fortran foram incorporadas a essas linguagens a partir de ideias originadas em Lisp. Com um detalhe: há ideias originadas em Lisp que só vieram achar seu caminho nas linguagens modernas nos últimos 10 ou 15 anos. Curiosamente, há ideias que ainda podem vir a ser incorporadas hoje.

Dentre as várias ideias inicialmente implementadas (ou até mesmo concebidas) em Lisp antes de serem abraçadas por outras linguagens merecem destaque:

Condicionais
Sim… Lisp foi a primeira linguagem a introduzir o agora onipresente if <condição>..then..else. Antes de Lisp, o que existia era um desvio condicional que permitia apenas redirecionar o fluxo de execução para uma linha dada cdo código (um goto, basicamente) com base no valor de uma posição de memória ou de algum registrador. Não havia o conceito de expressões lógicas e condicionais no sentido em que as usamos hoje.
Garbage collection
Quase toda linguagem moderna, em particular linguagens OO, usam algoritmos de coleta de lixo para gerenciar automaticamente os objetos alocados na memória. Lisp foi a primeira a propor um garbage collector e McCarthy é creditado como o inventor do mais antigo algoritmo para esse propósito: o famoso mark and sweep.
Estruturas de dados dinâmicas
Hoje assumimos que listas, árvores, conjuntos e mapas dinâmicos são as estruturas de dados básicos que qualquer linguagem de programação oferece. No início, contudo, apenas valores escalares (individuais) e arrays de tamanho fixo eram suportados pelas linguagens. Lisp foi a primeira linguagem a disseminar a importância do uso de estruturas dinâmicas para programar sistemas complexos. E isso é conseguido, usando apenas listas como a base de todas as estruturas dinâmicas. (A bem da verdade, é importante registrar que IPL, uma linguagem antiga tipo assembly, permitia uma forma primitiva de listas e foi a inspiração de McCarthy para Lisp).
Funções e recursividade
Desde a primeira versão, em 1958, As primeiras “funções” suportadas por linguagens de programação não eram funções propriamente ditas, capazes de receber argumentos e retornar um valor. Eram subrotinas (ou procedimentos) usados exclusivamente pelos efeitos colaterais produzidos. Lisp foi a primeira linguagem a suportar funções no sentido moderno, matemático do conceito, com suporte a argumentos e valor de retorno. Além disso, pela primeira vez a invocação de função passa a ser uma expressão e não um comando. Por fim, Lisp foi a primeira também a dar suporte a funções recursivas.
Funções de primeira classe e lambdas
Lisp também foi a primeira linguagem a suportar funções como entidades de primeira classe e o conceito de lambdas (ou funções anônimas) que são a base para suportar o que hoje se conhece como programação funcional.
Code as data
Lisp foi a primeira linguagem de programação homoicônica. Apesar do nome complicado, o conceito é (relativamente) simples: uma linguagem é dita homoicônica se a sintaxe usada para representar código na linguagem é a mesma usada pela linguagem para representar dados nessa linguagem. Suporte a código como dados garante que a linguagem reúne as condições básicas para manipular código escrito para ela própria, com a mesma facilidade com que manipula outros dados. Em Lisp, tanto código como dados são essencialmente representados como listas e escritos com a mesma sintaxe básica. Essa propriedade é a base para o que se chama de metaprogramação e programação de alta ordem.
REPL e código interpretado
Lisp é a linguagem que introduziu a cultura do REPL que se estende a muitas das linguagens modernas (tais como Python, Ruby, etc). A ideia de uma linguagem interativa com interpretação imediata do que o usuário digita em um REPL (Read Eval Print Loop) se tornou possível graças às ideias originais de especificação matemática da própria linguagem, feitas por McCarthy. Um estudante da equipe de McCarthy (ninguém menos que Steven Russel) percebeu que algumas das funções da especificação escritas elas próprias em Lisp poderiam ser implementadas em linguagem de máquina dando origem a um interpretador para a linguagem. Detalhe: McCarthy conta que disse que Russel estava confundindo teoria com prática, mas que Russel o ignorou e implementou o interpretador.
Tipagem dinâmica
Lisp foi a primeira linguagem de programação a suportar a tipagem dinâmica de variáveis. Ou seja, os tipos de dados associados com as variáveis não precisam ser definidos explicitamente pelo programador e são determinados em tempo de execução, de acordo com o tipo do valor efetivamente associado à variável quando o programa está em execução.
Suporte à metaprogramação
Pelos motivos mencionados acima, era até natural que Lisp evoluísse para proporcionar mecanismos de metaprogramação. Tal mecanismo, que recebe o nome de macros em Lisp, é, para alguns, uma das características mais importantes da linguagem, porque, na prática, combinado à sintaxe minimalista, permitem a extensão da linguagem. Lisp é frequentemente citada com uma das linguagens mais importantes no contexto das chamadas DSLs (Domain Specific Languages), dado que permite a criação de linguagens específicas para esses fins que compartilham da sintaxe básica de Lisp.
Outras
Ao longo dos anos, Lisp e seus inúmeros dialetos, têm sido a fonte de várias melhorias e avanços em linguagens de programação que depois se estenderam a outras linguagens. Algumas delas são: o conceito e a correta implementação de escopo léxico, o uso de closures, otimização de chamadas de cauda para recursividade eficiente (tail call optimization), dentre outras.

Conhecer e ser capaz de programar com a a linguagem que reúne tanto da história da programação e das linguagens de programação me parece ser fundamental para a formação de um bom profissional em computação. De fato, só este motivo já me parece mais que suficiente para aprender Clojure que, lembre, é um dialeto Lisp.

Mas há mais motivos pra estudar Clojure!

Até o momento só listei motivos para estudar Lisp, não Clojure propriamente dita. Isso pode fazer parecer que qualquer dialeto Lisp daria no mesmo. De fato, se o objetivo for apenas conhecer Lisp pelos motivos mais acadêmicos mencionados acima, sim, qualquer dialeto Lisp pode proporcionar resultados semelhantes. Contudo, há vários motivos para estudar Clojure, especificamente. Vamos a eles.

  1. Clojure é, provavelmente, o dialeto Lisp mais moderno de Lisp e está em franca utilização hoje, (talvez dispute com Racket, mas desconfio que Clojure é maior).

  2. Clojure pode ser usada tanto de forma interpretada, através do REPL (a la Lisp clássico), como de forma compilada. Em ambos os casos, a linguagem é extremamente eficiente. Isso estimula o uso do REPL em ambientes de desenvolvimento, experimentação e debugging e o uso de versões compiladas para bytecodes em produção. (Clojure é, na verdade, sempre compilada).

  3. Clojure é uma linguagem hospedada pela JVM (Java Virtual Machine). Isso permite que Clojure combine a expressividade de Lisp à eficiência com que Java pode ser executada. Além disso, permite ainda a interoperatividade nativa com objetos Java. Código Clojure pode facilmente acessar objetos Java e vice versa.

  4. Clojure tem suporte nativo para concorrência e paralelismo. É fato conhecido que programar com funções puras, sem efeito colateral e com dados imutávies é base para um bom modelo de concorrência e execução paralela. Isso, contudo, raramente é, de fato, explorado por outros dialetos Lisp e até por outras linguagens de programação (funcionais ou não). Clojure explora exatamente isso e o fato de que a JVM proporciona excelente suporte para programação concorrente e execução paralela. Como resultado, Clojure provê excelentes mecanismos para programação concorrente construídos sobre essa base: estruturas de dados imutáveis e persistentes, suporte a threads, memória transacional em software, futures, agentes, atoms, programação assíncrona.

  5. Clojure tem uma comunidade forte e ativa que mantém um ecossistema inteiro de ferramentas e bibliotecas em praticamente todos os nichos imagináveis (IA, data science, estatística e gráficos, web, games, etc). Em particular, há ferramental que permite compilar Clojure para JavaScript, de forma que pode ser usada, inclusive, no frontend (ClojureScript). Além disso, há excelente e vasta documentação na forma de sites, cursos e livros.

6. E, por fim, há ofertas de empregos sendo ofertados na indústria para programadores Clojure. Sim! Várias empresas de ponta procuram desenvolvedores Clojure. Certamente, pode ser um bom diferencial ter alguma vivência em projetos com Clojure e Lisp em seu currículo.

Concluindo… Estudar Clojure, hoje, me parece a melhor opção para qualquer estudante de computação que queira estudar Lisp. Clojure é uma versão viva e ativa da linguagem que, além de preservar os elementos fundamentais de Lisp, tem seu próprio brilho e importância. No pior dos casos, espero que este curso habilite e motive você a ler o famoso SICP (a respeito do assunto, sugiro que você leia o famoso review de Peter Norvig na Amazon, sobre o SICP).

Bom, espero que esta aula tenha convencido você de que este curso é uma excelente oportunidade de formação para você. Na próxima aula, já mergulhamos diretamente na linguagem. Dedique-se e tire o melhor proveito possível.