Como é 2023, vou aprender Common Lisp, claro
(log.schemescape.com)- Dentro da linha de refletir sobre linguagens que ainda possam ser usadas no futuro, desta vez a escolha esteve menos ligada à praticidade e mais à vontade de experimentar diretamente um fluxo de desenvolvimento moderno apesar de antigo
- Janet chamou atenção como uma pequena linguagem de script da família Lisp/Clojure, mirando um espaço parecido com o do Lua, por causa da biblioteca padrão e da metaprogramação baseada em macros
- A portabilidade, a capacidade de embedding e as Parsing Expression Grammars, que pareciam vantagens do Janet, tinham aderência limitada às necessidades atuais, e alternativas já existentes como CHICKEN Scheme, CLISP, SBCL e Packrat já pareciam suficientes
- O ponto central do desenvolvimento guiado por REPL em Common Lisp é poder inspecionar e modificar pilha, variáveis e dados vivos em um estado de erro em tempo de execução, e depois retomar a execução interrompida
- Embora exista o custo de aprender uma nova linguagem e sua biblioteca padrão, concluiu-se que valeria a pena vivenciar isso diretamente em um ambiente em que esse fluxo de trabalho foi pensado desde o início
Por que o interesse migrou de Janet para Common Lisp
- Havia uma preocupação recorrente com future-proof programming languages para escrever código que continue útil no futuro, mas, ao criar e compartilhar algo, a postura era pragmática: escolher linguagens populares, portáveis e convenientes
- Ao mesmo tempo, outras linguagens e ferramentas também eram exploradas por diversão e experimentação
- Recentemente, ao encontrar o livro gratuito Janet for Mortals, surgiu o interesse por Janet
- Janet é uma linguagem de script relativamente pequena, inspirada em Lisp/Clojure, que busca um nicho semelhante ao do Lua
- Ela oferece uma biblioteca padrão de verdade, metaprogramação no estilo Lisp e execução em tempo de compilação por meio de macros
- O que mais atraía em Janet, além da sua “lispez”, era a portabilidade, a capacidade de embedding e as Parsing Expression Grammars
- Como no momento não havia necessidade de embutir uma linguagem, a capacidade de embedding não se tornou um fator decisivo
- Em termos de portabilidade, CHICKEN Scheme, CLISP, e Steel Bank Common Lisp também pareciam boas opções
- Para parsing, Packrat parecia uma escolha razoável
- No fim, restaram poucos motivos para escolher Janet em vez de algum Lisp já existente
O fluxo de trabalho guiado por REPL que levou ao aprendizado de Common Lisp
- Um texto sobre o fluxo de trabalho guiado por REPL, quase único do Common Lisp, serviu de motivação para o estudo
- A situação de exemplo é quando a função
foochama a funçãobar, ainda não definida, e ocorre um erro durante a execução defoo- Em ambientes antigos de Lisp ou Smalltalk, a interrupção de
fooleva a um breakloop - O breakloop é um REPL completo dentro do ambiente dinâmico da função interrompida
- É possível percorrer a pilha de chamadas interrompida e verificar as variáveis visíveis em cada stack frame
- Também é possível inspecionar e modificar os dados vivos do programa em execução
- Se a causa for um valor incorreto em uma variável ou campo, é possível alterar esse valor e retomar a função interrompida
- Também dá para definir a função
barausente dentro do REPL e então retomarfoopara obter um resultado razoável
- Em ambientes antigos de Lisp ou Smalltalk, a interrupção de
- Já haviam sido vistas várias funcionalidades de “edit and continue”, mas foi marcante perceber que o Common Lisp foi concebido tendo esse tipo de fluxo em mente
- A experiência prévia com depuração vinha evoluindo do
printf debuggingpara o uso de depuradores de verdadeprintf debuggingconsiste em inserir temporariamente código de log, recompilar e verificar o resultado da execução- Em situações em que não era possível modificar ou reiniciar o programa, foi necessário aprender a usar depuradores
- O depurador real é a ferramenta correta, mas configurar o ambiente de depuração pode ser doloroso ou às vezes impossível
- Common Lisp pareceu ser um ambiente que leva a depuração um passo além
- Já havia experiência em testar possíveis correções alterando memória dentro do depurador
- Mas a possibilidade de reescrever e aplicar patches no código dentro do processo em execução pareceu especialmente atraente
- O objetivo ao aprender Common Lisp é construir programas interativamente e verificar pessoalmente se esse modo de trabalho é agradável
- Não está claro se aprender uma nova linguagem e sua biblioteca padrão é realmente a melhor forma de explorar um novo fluxo de trabalho, mas também não há uma alternativa claramente melhor
- Outras linguagens podem ter integrações semelhantes entre REPL e editor
- Só que, como esses recursos talvez não tenham sido suportados desde o início e sim adicionados depois, podem ter mais bugs
- Mesmo que a experiência não agrade, ainda fica o fato de que a tentativa foi feita nas melhores condições, com as ferramentas padrão
- Os materiais que ajudaram no estudo de Common Lisp foram reunidos em outro texto: Common Lisp resources
1 comentários
Opiniões do Hacker News
O que gostei até agora em Common Lisp é que o sistema de condições é elegante, e os reinícios (restarts) facilitam controlar até código que está bem distante. O desenvolvimento guiado por REPL também é conveniente em situações em que não se sabe bem o que vai acontecer, por exemplo ao fazer parsing de uma fonte de dados desconhecida, porque dá para corrigir o código e continuar sem perder o contexto
Há muitas implementações e a interoperabilidade é boa; em um caso, consegui alternar entre SBCL, para velocidade, e CLISP, para reduzir o uso de memória. Um dos motivos pelos quais, para aprender Lisp, acabei tendendo mais a CL do que a Scheme foi justamente haver várias implementações compatíveis
Mesmo sendo iniciante em Emacs, a integração com Common Lisp foi excelente, e roda bem até no netbook muito lento que uso para desenvolvimento. Na era atual de computadores rápidos, VS Code e language servers, isso é uma vantagem menos importante, mas com certeza tem um ar retrofuturista
Também há pontos negativos. O gerenciador de pacotes mais popular, QuickLisp, é ok, mas fica aquém do nível de recursos a que nos acostumamos em ecossistemas de linguagens modernas. A própria linguagem ficou congelada no tempo, então muita coisa, como threads, sincronização e argumentos de linha de comando, precisa de bibliotecas de interoperabilidade. E eu realmente gostaria que o SBCL desse suporte a builds totalmente estáticos para que fosse possível distribuir binários também para distribuições Linux que não usam glibc
Se uma tarefa importante como obter código-fonte não oferece suporte a criptografia ou autenticação, acho que o QL dificilmente é uma opção para quem se preocupa com segurança
Outro problema é o SBCL ser hospedado no SourceForge. O SourceForge já injetou malware em arquivos de download no passado, então vejo isso também como uma questão de segurança. Hoje não parece haver um bom motivo para continuar usando o SourceForge, e não entendo muito bem por que um projeto importante ainda permanece lá
Gosto muito de Lisp, e especialmente de Common Lisp, mas coisas assim me afastaram, e também não parece haver muito interesse em corrigi-las
https://www.timmons.dev/posts/static-executables-with-sbcl.h...
https://www.timmons.dev/posts/static-executables-with-sbcl-v...
https://cons.io
Gerbil/Gambit Scheme consegue gerar binários totalmente estáticos como alternativa ao CL
Ele pode ser usado como uma biblioteca Quicklisp comum, como uma imagem core que inicia imediatamente, ou como binário. Scripts também podem ser executados quase instantaneamente, então há aspectos parecidos com o Babashka. Ainda estou lapidando os detalhes, e não é v1.0
O trecho do blog sobre “lidar com um erro em tempo de execução corrigindo o código quebrado no lugar, sem reiniciar” deveria ter sido mostrado aqui: https://www.youtube.com/watch?v=jBBS4FeY7XM
Se você roda uma computação longa e pesada e aparece um erro na última etapa, em vez de executar tudo de novo desde o começo, entra no depurador interativo, vai até a linha do erro, compila a função corrigida e depois volta ao depurador para retomar a execução a partir de um ponto no stack frame. Depois dá para ver o programa passar
M-é muito melhor do que otap>que Clojure oferece. Mesmo usando ferramentas como o Portal, vejo otap>mais como uma impressão bonita glorificadaEm termos de desempenho, Common Lisp também permite especificação de tipos estáticos, e o SBCL consegue gerar código muito eficiente quando se faz isso. Ajuda rodar
DISASSEMBLEno próprio código para ver o que de fato é emitido e otimizar. Em especial, pacotes como SB-SIMD e Loopus ajudam muito em aplicações de cálculo numérico:quackfor true, trate como um pato”, o que se encaixa bem em design de jogos no estilo entidade-componente-sistemaMas senti muita falta, ao longo do último ano, do jeito de desenvolver em Common Lisp e, sinceramente, do sistema de condições. Nem sou um hacker tão proficiente em CL, mas isso rapidamente virou algo natural. Gostaria que o CLOS e os tipos primitivos de CL combinassem melhor do que parece à primeira vista
No Cider, isso certamente é fácil de fazer, e imagino que as principais ferramentas de outros editores façam algo parecido de forma suficientemente boa
DISASSEMBLEaté certo pontoHá projetos auxiliares como https://github.com/Bronsa/tools.decompiler, e também há o JitWatch do OpenJDK (https://github.com/AdoptOpenJDK/jitwatch). Outras JVMs também têm ferramentas parecidas
Não é tão intuitivo quanto Lisp, mas ainda assim é possível
É ótimo ter criado algo esotérico que funciona bem e é atraente, mas agora ninguém vai querer mexer nisso, exceto pessoas cujo salário pode ser expresso pelo número de zeros
https://www.digitalocean.com/community/tutorials/java-repl-j...
https://github.com/waf/CSharpRepl
https://pub.dev/packages/interactive
Sem falar em Ruby, Python, PHP e Lua; até C++ tem um REPL, ainda que meio tosco: https://github.com/root-project/cling
Toda vez que digo que REPL já não é mais um diferencial para Lisp, levo downvote
https://github.com/kaveh808/kons-9
Só que essas redefinições e sobrescritas acabam poluindo o namespace, e em algum momento algo quebra e você precisa recarregar o servidor
Ele também tem muitos pontos fracos, mas não são piores que as desvantagens puramente técnicas de quase todas as outras linguagens e ambientes de desenvolvimento. Python e JS são mais usados e têm mais bibliotecas, mas sinto que a experiência de desenvolvimento é muito pior. Usei bastante C#, TS, Py, Hs e linguagens ainda mais esotéricas ao longo de vários anos, mas sempre que fico preso nelas ou seriamente irritado com coisas claramente ruins, acabo voltando para CL (SBCL + Emacs + Slime). Aí fico aliviado e tenho a certeza de que ainda existe algo bom no mundo
Agora estamos captando investimento para um produto que, de forma tola, inicializamos em TypeScript, mas a versão de lançamento será refeita em CL. Isso significa que, pelos próximos 3 a 5 anos antes do lançamento, vou trabalhar com CL e dentro de CL, e fico feliz por poder fazer coisas divertidas como implementar DSLs, geração de código, trabalhar com macros e implementar um analisador de tipos estático
Ao trabalhar em C#, do que você sente falta em Lisp?
Minha opinião radical é que a programação funcional não decolou amplamente porque, embora seja claramente boa para escrever programas, a maior parte do software de hoje não é “um programa que roda na linha de comando no meu PC/servidor até terminar”, e sim “um programa que inicia, reage à entrada do usuário e fica rodando até o usuário fechá-lo” ou “um programa que inicia e depois responde à rede ou a outro I/O automático até outro software mandar parar”
Fazer esse tipo de coisa em estilo puramente funcional é muito mais difícil, ou pelo menos foi assim nas implementações funcionais mais opinativas que usei. Porque já não se trata simplesmente de avaliar uma expressão; é preciso inicializar estado, reagir a I/O, atualizar estado ou realizar I/O adicional, e até usar paralelismo para monitoramento, recepção, I/O adicional e atualização de estado
Claro que não é impossível fazer isso em Lisp, mas, com base em alguns semestres de programação funcional na graduação e na experiência profissional de usar recursos funcionais em C++/Scala para resolver esses problemas, pareceu-me bem difícil fazer a programação funcional funcionar bem nesse tipo de aplicação, e essa inadequação me fez hesitar em me aprofundar mais em programação funcional
Independentemente de Common Lisp, muitas vezes precisei escrever código que gera código. Parece que Lisp brilharia nessa área, mas ainda não tive oportunidade de tentar. Como exemplos de projetos recentes que fiz antes de pensar em Common Lisp, há geração de código para validar um JSON Schema específico em um gerador de site estático e geração de JSX a partir de Markdown para usar em conteúdo de história de um jogo de programação
Sem falar nos incontáveis macros em C que venho usando
Também não é preciso jogar fora todos os bons recursos de outros estilos. Partes da programação funcional estão ficando cada vez mais comuns até em linguagens generalistas. Por exemplo, Rust também usa padrões funcionais em muitos casos
E Lisp também pode ser usada em estilo orientado a objetos ou imperativo, se você quiser. Não é Haskell
Vejo Lisp como ponto de partida, e Haskell como a coisa de verdade. Recomendo aprender esse estilo mesmo que você não escreva código de produção real nele
(let ((pair (cons 1 nil)))(setf (cdr pair) pair)(list (first pair) (second pair) (third pair)));; => (1 1 1)Se quiser algo que “compile para sempre”, escreva em ANSI C
Se quiser algo que “sobreviva para sempre”, escreva em Python 2.7 e transforme isso na espinha dorsal dos templates de infraestrutura de toda a organização. Pontos extras se forem módulos Ansible customizados
O que eu espero é que Lisp torne mais fácil expressar pensamentos em código, minimizando ou abstraindo miudezas e boilerplate. Para mim, expressões Lisp parecem a forma mais natural de expressar pensamentos
Tenho interesse principalmente em escrever apps com GUI, então quero aprender Clog ou encontrar/desenvolver um bom wrapper sobre algum toolkit de GUI
(do-it (do-it first))(do-it first)funciona bem, mas o que acontece se a chamada(do-it (do-it first))falhar?Seria bom obter o controle no ponto quebrado e corrigir a definição de
do-it. Mas, se você corrigir, o resultado de(do-it first)também mudaEntão o ponto em que a máquina está agora passa a ser um ponto que não é mais alcançável com o código atual
Eu realmente não sei como isso funciona quando a correção que permite continuar altera o próprio estado em que você recebeu o controle para fazer a correção