Skip to content

Instantly share code, notes, and snippets.

@asemic-horizon
Created March 3, 2021 13:54
Show Gist options
  • Save asemic-horizon/9a11450c0ee4f8a2d5253b153da48729 to your computer and use it in GitHub Desktop.
Save asemic-horizon/9a11450c0ee4f8a2d5253b153da48729 to your computer and use it in GitHub Desktop.
Eu sempre gostei de filmes que começam no meio da ação -- o que os manuais de roteiro chamam de in media res. O que tenho para contar hoje começa no meio de uma ampla revolução social chamada "Python". A crer no burburinho, até gerentes de supermercado precisam saber Python agora. Não me cabe (nem cabe aqui) explicar as razões, mas está acontecendo. No meio dessa comoção, há algo novo que vocês precisam descobrir.
O que é Hy? Como o Tao ou O Vestido (que é azul e preto), a resposta depende um pouco de quem é você.
Para os ousados, sedentos de aprendizado ou veteranos "poliglotas", Hy é uma linguagem de programação na tradição Lisp, completa com parênteses, notação prefixada e macros. Para os assustados, programadores que investiram tudo no Python ou gente que descobriu que ".fit_predict()" é o novo inglês fluente, é uma sintaxe alternativa para o Python, estruturada como um Lisp; é Python sob outra casca: onde na sintaxe padrão dizem
from numpy import exp
def f(x):
return 1/(1+exp(-x))
em Hy nós dizemos
(import [numpy [exp]])
(defn f [x] (/ 1 (+ 1 (exp x))))
... mas esses exemplos fazem pouca justiça: na vida real, Hy é assim:
No alt text provided for this image
... uma era mais civilizada
Uma das minhas lembranças favoritas dos desenhos animados da TV na minha infância é o episódio dos Flintstones em que a turma vai assistir a um show dos Rolling Stones, "a única banda de rock do mundo". Lisp tem uma vibe parecida: anunciada em 1958, tem entre seus coetâneos flechas de pedra lascada como ALGOL 58 (precursor do ALGOL 68, que é precursor de quase tudo que vocês entendem como código ou pseudocódigo). Nesse contexto, Lisp era estupidamente moderno, e segundo algumas caracterizações muito sólidas, nos 60 anos intervenientes as linguagens de programação foram lenta e dolorosamente adquirindo os recursos do Lisp, sem ter chegado lá por completo.
Há muito material interessante por aí com definições filosóficas sobre o que torna Lisp tão especial (a expressão precisa é primum inter pares, o primeiro entre iguais) entre as linguagens de programação. Talvez o material técnico mais importante seja o monumental "Structure and Interpretation of Computer Programs" de Sussman e Abelson, o curso hardcore de introdução à programação do MIT que às vezes aparece, em versão diluída para a realidade nacional, nas graduações brasileiras em computação ou engenharia.
O irônico é que até o MIT abandonou o Lisp para o curso de introdução à programação -- bem como o famoso curso de inteligência artificial de Peter Norvig. O que essas pessoas recomendam hoje em dia? Python. E não é difícil entender por que -- basta perguntar ao não-programador que diz aos amigos no Starbucks que está na hora de aprender...
A tempestade perfeita
A migração em massa para o Python tem, portanto, essas duas pontas extremas: como upgrade do Excel ou substituto (ao menos um pouco pior) do Lisp. Como bem se sabe, a adoção de linguagens tem uma dinâmica do tipo rico-fica-mais-rico. Analistas de dados adotam Python porque é um dos melhores ecossistemas para ETL, BI, data science e outros termos da moda (há quem prefira o R, mas por alguma razão nem o Peter Norvig nem o gerente de supermercado se interessam); as bibliotecas se tornam ricas, eficientes e bem testadas (praticamente livres de bugs, portanto) porque Python é amplamente usado, o que torna Python mais atraente.
Sob essa ótica, Hy é (disputando com Clojure, que vive no mundo Java) o Lisp mais atraente do mundo: pandas, networkx, frameworks web... tudo, tudo, tudo lá. É mais difícil, claro, convencer você, que sabe porque quer Python, que deveria usar Python de um jeito Lisp. O fato é que existe toda uma literatura procurando explicar -- para pessoas que teriam que trocar de ecossistema -- por que Lisp é preferível. Como começar a usar Hy é fácil e indolor para quem já está afundado em Python até a cintura, a minha tarefa deve ser mais fácil.
Argumento 1: parênteses coloridos
Ao contrário de linguagens como C e JavaScript, que delimitam tudo com parênteses, colchetes e pontuação, a organização de código Python depende fortemente da formatação do código. Os defensores dizem que o tipo de indentação semântica que se pratica em Python é, de qualquer modo, a melhor prática em qualquer linguagem, e em pouco tempo o usuário se acostuma. Eu concordo -- uso, afinal, muito Python.
Mas a organização do código por parênteses explícitos explicita uma tarefa cognitiva que quando espaços brancos são significativos é implícita, mas não por isso menos constante. A filosofia de sintaxe do Hy, seguindo as tradições Lisp é simples, bastando seguir as duas regras:
Regra 1. Código é feito de formas. Formas são de dois tipos: átomos que podem ser avaliados imediatamente e expressões entre parênteses. Nesse caso, o primeiro átomo deve ser algo que pode ser chamado.
2 ;; ok
(2) ;; não! equivalente em Python a "2()"
(2 3) ;; não! equivalente em Python a "2(3)"
(+ 2 3) ok! equivalente a "2 + 3"
Regra 2. A Regra 1 dentro da (Regra 1 dentro da (Regra 1 .... ))))
(.from_dict pandas {"nome" (get-db name) "idade" (* 7 ( - 3 (random.poisson 7)))})
Os parênteses acumulados são difíceis de administrar, é verdade. Mas o seu editor de código preferido já tem as ferramentas necessárias para administrar esse problema:
Rainbow brackets: Como já vimos em uma captura de tela acima, a coloração em arco-íris dos parênteses retorna um feedback visual que com poucos dias de prática permite visualizar toda a estrutura do código que está na sua tela com um correr de olhos:
No alt text provided for this image
Neste exemplo artificialmente simples, podemos ver a definição da função em uma linha só, ou indentada de maneira visualmente conveniente. Em ambos casos, as cores mantêm o tal "fio da meada" e é fácil saber o escopo de cada coisa.
Parinfer. Existe (por um teorema) uma relação biunívoca entre indentação lógica e parênteses. Desse modo, o algoritmo de Parinfer (disponível em todos os editores) permite que o computador (a) mantenha os parênteses corretos, desde que você faça a indentação correta ou (b) indente o código corretamente, desde que você se encarregue dos parênteses. É uma combinação poderosíssima: ao escrever código novo, eu faço a indentação e deixo os parênteses a cargo do editor; ao interferir em código existente, faço o contrário.
Há aí um pequeno paradoxo: embora a sintaxe tipo Lisp pareça aumentar a complexidade do código, a dificuldade efetiva de se lidar com código é menor, já que a sintaxe Lisp é axiomaticamente definida e pode ser analisada de maneira inteligente (e axiomática, sem heurísticas ou "hacks") pelo "cérebro eletrônico" do computador.
Argumento 2: homoiconicidade
Por que existem ferramentas tão poderosas para editar Lisp? Porque sua sintaxe é bem-definida de um ponto de vista axiomático, permitindo raciocinar sobre ela e transformá-la de maneira simples. Isso não se limita a editores: computadores lidam bem com árvores (e a Regra 2 diz que Lisp é uma árvore de formas), o que inclui o próprio Lisp. Isso permite escrever código Hy que escreve ou transforma código Hy, permitindo estender a linguagem de forma ilimitada. Darei dois exemplos.
O primeiro é a chamada macro de threading, que o Hy copiou do Clojure. Compare as seguintes linhas:
sqrt(abs(log(abs(x))) # em Python
(sqrt (abs (log (abs x)))) ;; em Hy, cheio de parênteses coloridos
(-> x abs log abs sqrt) ;; em Hy, usando a macro de threading
A macro de threading funciona como o olho de uma agulha: costura "x" em "abs", e depois em "log", e assim sucessivamente. Algumas pessoas vão reconhecer nisso a notação RPN das máquinas de calcular HP; outras,/ mais curiosas sobre linguagens de programação vão notar que a "agulha" define uma linguagem concatenativa, a exemplo do Forth e do Factor. De fato, programar Lisp (e em particular Hy) é em parte definir pequenas linguagens de programação.
Vamos criar uma macro nós mesmos? Digamos que queremos transformar operações do tipo "(+ a b)" em "(+ b a)". Isso seria inútil, porque a adição é comutativa. Mas podemos definir essa transformação para expressões binárias arbitrárias:
No alt text provided for this image
Com essa definição, podemos dizer, por exemplo:
(troca (/ 2 4)) ;; igual a (/ 4 2) == 2
Por que eu faria isso? Bem, muitas vezes o que se quer é dividir uma grande expressão por 2, e é menos confuso se o denominador não for um átomo sozinho quatro linhas depois. Mas essa macro também serve para definir um conceito útil na matemática chamado comutador: se (a,b) é uma expressão binária, o comutador é (a,b) - (b,a). Para operações comutativas como a soma, o comutador é sempre zero; em outros casos, como o da multiplicação de matrizes (que às vezes comuta e às vezes não), o comutador dá uma medida da não-comutatividade do processo. Com a macro acima, basta definir
No alt text provided for this image
A forma especial "deftag" cria uma macro unária que não demanda parêntese. Assim, podemos dizer
#L(/ 2 8) ;; equivale a 8/2 - 2/8 = 3.75
Parênteses coloridos: um paradoxo
Além do Algol 58 (precursor do Algol 68, precursor do Pascal e do C, precursores do....), existe outra linguagem notória que surgiu quase simultaneamente ao Lisp: Cobol (sigla que significa "linguagem comum orientada a negócios).
Cobol foi desenhada para ser fácil de usar e ensinar, adotando para isso uma notação que se parece muito com o inglês. Lisp, ao contrário, foi criado como uma representação útil para a máquina; na época, prometeu-se inclusive uma camada de interface amigável, em particular com operadores infixos para aritmética ("2 + 2" em vez de "+ 2 2").
De fato, a notação de prefixos não é muito natural dada a forma como aprendemos aritmética. Mas também não é tão complexa que não seja possível adquirir familiaridade e fluência. Como já vimos, o lucro que se ganha com essa notação é a homoiconicidade, que torna a linguagem metaprogramável. Mas isso não é tudo: em contraste ao Python padrão (mas em comum com o JavaScript e o C), código Hy pode ser indentado livremente, de modo que o espaço em branco pode ser usado para enfatizar características estruturais.
No alt text provided for this image
O resultado é que uma vez que a estranheza passa (e na realidade aritmética é uma parte minúscula do que se escreve), o código Hy pode ser mais legível que seus equivalentes em linguagens de sintaxe mais elaborada. É um paradoxo: o Cobol foi desenhado para ser legível -- e de fato, um não programador consegue adivinhar o que o código diz -- mas é difícil raciocinar sobre linguagem natural de maneira abstrata.
As linguagens de programação modernas reconhecem a importância de denotar abstrações e oferecem recursos para isso. Como uma notação alternativa para o Python, Hy herda sua miscelânea característica de recursos (funções, classes, iteradores, co-rotinas, decoradores...). Mas há regularidades que não são estruturalmente exatas, e outras que só vamos descobrir à medida que desenvolvemos
Daí o paradoxo: há muito se diz que código é escrito para seres humanos, sendo a capacidade de executá-lo uma consequência agradável; indentação semântica e parênteses coloridos fazem exatamente isso -- não porque Lisp seja uma linguagem acessível, mas porque não toma muitas decisões sobre como você deve se expressar. Assim, quando eu olho o meu código Hy, vejo na tela as minhas idéias, e a máquina me ajuda a pensar.
Como prosseguir
Esse pequeno milagre pode ser reproduzido na sua casa ou escritório digitando
pip install hy
Incrivelmente, é só isso.
O tutorial para Pythonistas explica todos os aspectos concretos sobre a linguagem que não expliquei neste ensaio. É útil também no início checar o seu código com o utilitário hy2py, que transforma o seu código Hy em Python plausível, e virá de brinde no pacote.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment