Code Writing Code: Uma Introdução à Teoria e Prática da Metaprogramação Moderna

Publicados: 2022-07-22

Sempre que penso na melhor maneira de explicar macros, lembro de um programa em Python que escrevi quando comecei a programar. Não consegui organizar como queria. Eu tive que chamar várias funções ligeiramente diferentes, e o código se tornou complicado. O que eu estava procurando - embora não soubesse disso na época - era metaprogramação .

metaprogramação (nome feminino)

Qualquer técnica pela qual um programa pode tratar código como dados.

Podemos construir um exemplo que demonstre os mesmos problemas que enfrentei com meu projeto Python imaginando que estamos construindo o back-end de um aplicativo para donos de animais de estimação. Usando as ferramentas em uma biblioteca, pet_sdk , escrevemos Python para ajudar os donos de animais de estimação a comprar comida de gato:

 import pet_sdk cats = pet_sdk.get_cats() print(f"Found {len(cats)} cats!") for cat in cats: pet_sdk.order_cat_food(cat, amount=cat.food_needed)
Fragmento 1: Pedir comida de gato

Depois de confirmar que o código funciona, passamos a implementar a mesma lógica para mais dois tipos de animais de estimação (pássaros e cães). Também adicionamos um recurso para agendar consultas veterinárias:

 # An SDK that can give us information about pets - unfortunately, the functions are slightly different for each pet import pet_sdk # Get all of the birds, cats, and dogs in the system, respectively birds = pet_sdk.get_birds() cats = pet_sdk.get_cats() dogs = pet_sdk.get_dogs() for cat in cats: print(f"Checking information for cat {cat.name}") if cat.hungry(): pet_sdk.order_cat_food(cat, amount=cat.food_needed) cat.clean_litterbox() if cat.sick(): available_vets = pet_sdk.find_vets(animal="cat") if len(available_vets) > 0: vet = available_vets[0] vet.book_cat_appointment(cat) for dog in dogs: print(f"Checking information for dog {dog.name}") if dog.hungry(): pet_sdk.order_dog_food(dog, amount=dog.food_needed) dog.walk() if dog.sick(): available_vets = pet_sdk.find_vets(animal="dog") if len(available_vets) > 0: vet = available_vets[0] vet.book_dog_appointment(dog) for bird in birds: print(f"Checking information for bird {bird.name}") if bird.hungry(): pet_sdk.order_bird_food(bird, amount=bird.food_needed) bird.clean_cage() if bird.sick(): available_vets = pet_sdk.find_birds(animal="bird") if len(available_vets) > 0: vet = available_vets[0] vet.book_bird_appointment(bird)
Trecho 2: Peça comida de gato, cachorro e pássaro; Marcar consulta com veterinário

Seria bom condensar a lógica repetitiva do Snippet 2 em um loop, então começamos a reescrever o código. Rapidamente percebemos que, como cada função tem um nome diferente, não podemos determinar qual delas (por exemplo, book_bird_appointment , book_cat_appointment ) chamar em nosso loop:

 import pet_sdk all_animals = pet_sdk.get_birds() + pet_sdk.get_cats() + pet_sdk.get_dogs() for animal in all_animals: # What now?
Trecho 3: E agora?

Vamos imaginar uma versão turbinada do Python na qual podemos escrever programas que geram automaticamente o código final que queremos — um no qual podemos manipular nosso programa de maneira flexível, fácil e fluida como se fosse uma lista, dados em um arquivo ou qualquer outro tipo de dado comum ou entrada de programa:

 import pet_sdk for animal in ["cat", "dog", "bird"]: animals = pet_sdk.get_{animal}s() # When animal is "cat", this # would be pet_sdk.get_cats() for animal in animal: pet_sdk.order_{animal}_food(animal, amount=animal.food_needed) # When animal is "dog" this would be # pet_sdk.order_dog_food(dog, amount=dog.food_needed)
Trecho 4: TurboPython: um programa imaginário

Este é um exemplo de macro , disponível em linguagens como Rust, Julia ou C, para citar algumas, mas não Python.

Este cenário é um ótimo exemplo de como pode ser útil escrever um programa capaz de modificar e manipular seu próprio código. Essa é precisamente a atração das macros, e é uma das muitas respostas para uma pergunta maior: como podemos fazer um programa fazer introspecção em seu próprio código, tratando-o como dados, e então agir de acordo com essa introspecção?

De modo geral, todas as técnicas que podem realizar tal introspecção se enquadram no termo genérico “metaprogramação”. A metaprogramação é um subcampo rico em design de linguagem de programação e pode ser rastreada até um conceito importante: código como dados.

Reflexão: em defesa do Python

Você pode apontar que, embora o Python possa não fornecer suporte a macros, ele oferece muitas outras maneiras de escrever esse código. Por exemplo, aqui usamos o método isinstance() para identificar a classe da qual nossa variável animal é uma instância e chamar a função apropriada:

 # An SDK that can give us information about pets - unfortunately, the functions # are slightly different import pet_sdk def process_animal(animal): if isinstance(animal, pet_sdk.Cat): animal_name_type = "cat" order_food_fn = pet_sdk.order_cat_food care_fn = animal.clean_litterbox elif isinstance(animal, pet_sdk.Dog): animal_name_type = "dog" order_food_fn = pet_sdk.order_dog_food care_fn = animal.walk elif isinstance(animal, pet_sdk.Bird): animal_name_type = "bird" order_food_fn = pet_sdk.order_bird_food care_fn = animal.clean_cage else: raise TypeError("Unrecognized animal!") print(f"Checking information for {animal_name_type} {animal.name}") if animal.hungry(): order_food_fn(animal, amount=animal.food_needed) care_fn() if animal.sick(): available_vets = pet_sdk.find_vets(animal=animal_name_type) if len(available_vets) > 0: vet = available_vets[0] # We still have to check again what type of animal it is if isinstance(animal, pet_sdk.Cat): vet.book_cat_appointment(animal) elif isinstance(animal, pet_sdk.Dog): vet.book_dog_appointment(animal) else: vet.book_bird_appointment(animal) all_animals = pet_sdk.get_birds() + pet_sdk.get_cats() + pet_sdk.get_dogs() for animal in all_animals: process_animal(animal)
Trecho 5: um exemplo idiomático

Chamamos esse tipo de reflexão de metaprogramação e voltaremos a ele mais tarde. O código do Snippet 5 ainda é um pouco complicado, mas mais fácil para um programador escrever do que o do Snippet 2, no qual repetimos a lógica para cada animal listado.

Desafio

Usando o método getattr , modifique o código anterior para chamar as funções apropriadas order_*_food e book_*_appointment dinamicamente. Isso provavelmente torna o código menos legível, mas se você conhece bem o Python, vale a pena pensar em como você pode usar getattr em vez da função isinstance e simplificar o código.


Homoiconicidade: a importância do Lisp

Algumas linguagens de programação, como Lisp, levam o conceito de metaprogramação para outro nível via homoiconicidade .

homoiconicidade (substantivo)

A propriedade de uma linguagem de programação pela qual não há distinção entre o código e os dados nos quais um programa está operando.

Lisp, criada em 1958, é a linguagem homoicônica mais antiga e a segunda linguagem de programação de alto nível mais antiga. Obtendo o nome de “LISt Processor”, Lisp foi uma revolução na computação que moldou profundamente como os computadores são usados ​​e programados. É difícil exagerar o quão fundamental e distintamente o Lisp influenciou a programação.

O Emacs é escrito em Lisp, que é a única linguagem de computador que é bonita. Neal Stephenson

Lisp foi criado apenas um ano após o FORTRAN, na era dos cartões perfurados e computadores militares que enchiam uma sala. No entanto, os programadores ainda usam o Lisp hoje para escrever aplicativos novos e modernos. O principal criador do Lisp, John McCarthy, foi um pioneiro no campo da IA. Por muitos anos, Lisp foi a linguagem da IA, com pesquisadores valorizando a capacidade de reescrever dinamicamente seu próprio código. A pesquisa de IA de hoje está centrada em redes neurais e modelos estatísticos complexos, em vez desse tipo de código de geração lógica. No entanto, a pesquisa feita em IA usando Lisp – especialmente a pesquisa realizada nos anos 60 e 70 no MIT e Stanford – criou o campo como o conhecemos, e sua enorme influência continua.

O advento do Lisp expôs os primeiros programadores às possibilidades computacionais práticas de coisas como recursão, funções de ordem superior e listas vinculadas pela primeira vez. Também demonstrou o poder de uma linguagem de programação construída sobre as ideias do cálculo lambda.

Essas noções desencadearam uma explosão no design de linguagens de programação e, como disse Edsger Dijkstra, um dos maiores nomes da ciência da computação, [...] ajudou vários de nossos companheiros humanos mais talentosos a pensar em pensamentos anteriormente impossíveis”.

Este exemplo mostra um programa Lisp simples (e seu equivalente na sintaxe Python mais familiar) que define uma função “fatorial” que calcula recursivamente o fatorial de sua entrada e chama essa função com a entrada “7”:

Lisp Pitão
( defun factorial ( n ) ( if ( = n 1 ) 1 ( * n ( factorial ( - n 1 ))))) ( print ( factorial 7 ))
 def factorial (n) : if n == 1 : return 1 else : return n * factorial(n -1 ) print(factorial( 7 ))

Codifique como dados

Apesar de ser uma das inovações mais impactantes e conseqüentes do Lisp, a homoiconicidade, ao contrário da recursão e de muitos outros conceitos pioneiros do Lisp, não chegou à maioria das linguagens de programação atuais.

A tabela a seguir compara funções homoicônicas que retornam código em Julia e Lisp. Julia é uma linguagem homoicônica que, em muitos aspectos, se assemelha às linguagens de alto nível com as quais você pode estar familiarizado (por exemplo, Python, Ruby).

A peça chave da sintaxe em cada exemplo é seu caractere de aspas . Julia usa um : (dois pontos) para citar, enquanto Lisp usa um ' (aspas simples):

Júlia Lisp
function function_that_returns_code() return :(x + 1 ) end
 ( defun function_that_returns_code () '(+ x 1 ))

Em ambos os exemplos, a citação ao lado da expressão principal ( (x + 1) ou (+ x 1) ) a transforma do código que teria sido avaliado diretamente em uma expressão abstrata que podemos manipular. A função retorna código—não uma string ou dados. Se fôssemos chamar nossa função e escrever print(function_that_returns_code()) , Julia imprimiria o código stringified como x+1 (e o equivalente é verdadeiro para Lisp). Por outro lado, sem o : (ou ' em Lisp), obteríamos um erro de que x não foi definido.

Vamos voltar ao nosso exemplo de Julia e estendê-lo:

 function function_that_returns_code(n) return :(x + $n) end my_code = function_that_returns_code(3) print(my_code) # Prints out (x + 3) x = 1 print(eval(my_code)) # Prints out 4 x = 3 print(eval(my_code)) # Prints out 6
Trecho 6: Exemplo de Julia estendido

A função eval pode ser usada para executar o código que geramos de outro lugar no programa. Observe que o valor impresso é baseado na definição da variável x . Se eval nosso código gerado em um contexto em que x não foi definido, obteremos um erro.

A homoiconicidade é um tipo poderoso de metaprogramação, capaz de desbloquear novos e complexos paradigmas de programação nos quais os programas podem se adaptar em tempo real, gerando código para atender a problemas específicos de domínio ou novos formatos de dados encontrados.

Veja o caso do WolframAlpha, onde a homoicônica Wolfram Language pode gerar código para se adaptar a uma incrível variedade de problemas. Você pode perguntar ao WolframAlpha: “Qual é o PIB da cidade de Nova York dividido pela população de Andorra?” e, notavelmente, recebem uma resposta lógica.

Parece improvável que alguém pense em incluir esse cálculo obscuro e inútil em um banco de dados, mas Wolfram usa metaprogramação e um gráfico de conhecimento ontológico para escrever código em tempo real para responder a essa pergunta.

É importante entender a flexibilidade e o poder que o Lisp e outras linguagens homoicônicas fornecem. Antes de nos aprofundarmos, vamos considerar algumas das opções de metaprogramação à sua disposição:

Definição Exemplos Notas
Homoiconicidade Uma característica de linguagem na qual o código é dado de “primeira classe”. Como não há separação entre código e dados, os dois podem ser usados ​​de forma intercambiável.
  • Lisp
  • Prólogo
  • Júlia
  • Rebol/Vermelho
  • Língua Wolfram
Aqui, Lisp inclui outras linguagens da família Lisp, como Scheme, Racket e Clojure.
Macros Uma instrução, função ou expressão que recebe código como entrada e retorna código como saída.
  • macro_rules! , Derive e macros procedurais
  • Invocações @macro de Julia
  • defmacro de Lisp
  • C's #define
(Veja a próxima nota sobre as macros de C.)
Diretivas de pré-processador (ou pré-compilador) Um sistema que recebe um programa como entrada e, com base nas instruções incluídas no código, retorna uma versão alterada do programa como saída.
  • macros de C
  • # sistema de pré-processador de C++
As macros de C são implementadas usando o sistema de pré-processador de C, mas os dois são conceitos separados.

A principal diferença conceitual entre as macros de C (nas quais usamos a diretiva de pré-processador #define ) e outras formas de diretivas de pré-processador de C (por exemplo, #if e #ifndef ) é que usamos as macros para gerar código enquanto usamos outras diretivas não #define . diretivas de pré-processador para compilar condicionalmente outro código. Os dois estão intimamente relacionados em C e em algumas outras linguagens, mas são tipos diferentes de metaprogramação.
Reflexão A capacidade de um programa de examinar, modificar e introspectar seu próprio código.
  • As isinstance , getattr , do Python
  • Reflect e typeof do JavaScript
  • getDeclaredMethods do Java
  • Hierarquia de classe System.Type do .NET
A reflexão pode ocorrer em tempo de compilação ou em tempo de execução.
Genéricos A capacidade de escrever código válido para vários tipos diferentes ou que pode ser usado em vários contextos, mas armazenado em um só lugar. Podemos definir os contextos nos quais o código é válido explicitamente ou implicitamente.

Genéricos de estilo de modelo:

  • C++
  • Ferrugem
  • Java

Polimorfismo paramétrico:

  • Haskell
  • ML
A programação genérica é um tópico mais amplo do que a metaprogramação genérica, e a linha entre as duas não está bem definida.

Na visão deste autor, um sistema de tipos paramétricos só conta como metaprogramação se estiver em uma linguagem de tipagem estática.
Uma referência para metaprogramação

Vejamos alguns exemplos práticos de homoiconicidade, macros, diretivas de pré-processador, reflexão e genéricos escritos em várias linguagens de programação:

 # Prints out "Hello Will", "Hello Alice", by dynamically creating the lines of code say_hi = :(println("Hello, ", name)) name = "Will" eval(say_hi) name = "Alice" eval(say_hi)
Trecho 7: Homoiconicidade em Julia
 int main() { #ifdef _WIN32 printf("This section will only be compiled for and run on windows!\n"); windows_only_function(); #elif __unix__ printf("This section will only be compiled for and run on unix!\n"); unix_only_function(); #endif printf("This line runs regardless of platform!\n"); return 1; }
Trecho 8: Diretivas de pré-processador em C
 from pet_sdk import Cat, Dog, get_pet pet = get_pet() if isinstance(pet, Cat): pet.clean_litterbox() elif isinstance(pet, Dog): pet.walk() else: print(f"Don't know how to help a pet of type {type(pet)}")
Trecho 9: Reflexão em Python
 import com.example.coordinates.*; interface Vehicle { public String getName(); public void move(double xCoord, double yCoord); } public class VehicleDriver<T extends Vehicle> { // This class is valid for any other class T which implements // the Vehicle interface private final T vehicle; public VehicleDriver(T vehicle) { System.out.println("VehicleDriver: " + vehicle.getName()); this.vehicle = vehicle; } public void goHome() { this.vehicle.move(HOME_X, HOME_Y); } public void goToStore() { this.vehicle.move(STORE_X, STORE_Y); } }
Trecho 10: Genéricos em Java
 macro_rules! print_and_return_if_true { ($val_to_check: ident, $val_to_return: expr) => { if ($val_to_check) { println!("Val was true, returning {}", $val_to_return); return $val_to_return; } } } // The following is the same as if for each of x, y, and z, // we wrote if x { println!...} fn example(x: bool, y: bool, z: bool) -> i32 { print_and_return_if_true!(x, 1); print_and_return_if_true!(z, 2); print_and_return_if_true!(y, 3); }
Trecho 11: Macros em Rust

Macros (como a do Snippet 11) estão se tornando populares novamente em uma nova geração de linguagens de programação. Para desenvolvê-los com sucesso, devemos considerar um tópico-chave: higiene.

Macros higiênicos e anti-higiênicos

O que significa um código ser “higiênico” ou “anti-higiênico”? Para esclarecer, vamos ver uma macro Rust, instanciada pela macro_rules! função. Como o nome indica, macro_rules! gera código baseado em regras que definimos. Neste caso, nomeamos nossa macro my_macro e a regra é “Crie a linha de código let x = $n ”, onde n é nossa entrada:

 macro_rules! my_macro { ($n) => { let x = $n; } } fn main() { let x = 5; my_macro!(3); println!("{}", x); }
Trecho 12: Higiene na Ferrugem

Quando expandimos nossa macro (executando uma macro para substituir sua invocação pelo código que ela gera), esperaríamos obter o seguinte:

 fn main() { let x = 5; let x = 3; // This is what my_macro!(3) expanded into println!("{}", x); }
Trecho 13: Nosso exemplo, expandido

Aparentemente, nossa macro redefiniu a variável x para igual a 3, portanto, podemos esperar razoavelmente que o programa imprima 3 . Na verdade, imprime 5 ! Surpreso? Em Rust, macro_rules! é higiênico em relação aos identificadores, portanto, não “capturaria” identificadores fora de seu escopo. Nesse caso, o identificador era x . Se tivesse sido capturado pela macro, teria sido igual a 3.

higiene (nome masculino)

Uma propriedade que garante que a expansão de uma macro não capturará identificadores ou outros estados além do escopo da macro. Macros e macrossistemas que não fornecem essa propriedade são chamados de anti- higiênicos .

A higiene nas macros é um tema um tanto controverso entre os desenvolvedores. Os proponentes insistem que, sem higiene, é muito fácil modificar sutilmente o comportamento do seu código por acidente. Imagine uma macro que é significativamente mais complexa do que o Snippet 13 usado em código complexo com muitas variáveis ​​e outros identificadores. E se essa macro usasse uma das mesmas variáveis ​​do seu código — e você não percebesse?

Não é incomum que um desenvolvedor use uma macro de uma biblioteca externa sem ter lido o código-fonte. Isso é especialmente comum em linguagens mais recentes que oferecem suporte a macros (por exemplo, Rust e Julia):

 #define EVIL_MACRO website="https://evil.com"; int main() { char *website = "https://good.com"; EVIL_MACRO send_all_my_bank_data_to(website); return 1; }
Trecho 14: Uma Macro C do Mal

Essa macro anti-higiênica em C captura o website identificador e altera seu valor. É claro que a captura de identificadores não é maliciosa. É apenas uma consequência acidental do uso de macros.

Então, macros higiênicas são boas e macros não higiênicas são ruins, certo? Infelizmente, não é tão simples. Há um forte argumento a ser feito de que macros higiênicas nos limitam. Às vezes, a captura de identificador é útil. Vamos revisitar o Snippet 2, onde usamos pet_sdk para fornecer serviços para três tipos de animais de estimação. Nosso código original começou assim:

 birds = pet_sdk.get_birds() cats = pet_sdk.get_cats() dogs = pet_sdk.get_dogs() for cat in cats: # Cat specific code for dog in dogs: # Dog specific code # etc…
Trecho 15: De volta ao veterinário — Recuperando o pet sdk de estimação

Você deve se lembrar que o Snippet 3 foi uma tentativa de condensar a lógica repetitiva do Snippet 2 em um loop completo. Mas e se nosso código dependesse dos identificadores cats and dogs , e quiséssemos escrever algo como o seguinte:

 {animal}s = pet_sdk.get{animal}s() for {animal} in {animal}s: # {animal} specific code
Trecho 16: Captura de identificador útil (no imaginário "TurboPython")

O trecho 16 é um pouco simples, é claro, mas imagine um caso em que gostaríamos que uma macro escrevesse 100% de uma determinada parte do código. Macros higiênicos podem ser limitantes nesse caso.

Embora o debate macro higiênico versus anti-higiênico possa ser complexo, a boa notícia é que não é aquele em que você precisa tomar uma posição. A linguagem que você está usando determina se suas macros serão higiênicas ou não higiênicas, então tenha isso em mente ao usar macros.

Macros modernas

As macros estão tendo um momento agora. Por muito tempo, o foco das linguagens de programação imperativas modernas se afastou das macros como parte central de sua funcionalidade, evitando-as em favor de outros tipos de metaprogramação.

As linguagens que os novos programadores estavam aprendendo nas escolas (por exemplo, Python e Java) diziam a eles que tudo o que eles precisavam era reflexão e genéricos.

Com o tempo, à medida que essas linguagens modernas se tornaram populares, as macros se tornaram associadas à sintaxe intimidante do pré-processador C e C++ - se os programadores estivessem cientes delas.

Com o advento de Rust e Julia, no entanto, a tendência voltou para as macros. Rust e Julia são duas linguagens modernas, acessíveis e amplamente utilizadas que redefiniram e popularizaram o conceito de macros com algumas ideias novas e inovadoras. Isso é especialmente empolgante em Julia, que parece pronta para substituir Python e R como uma linguagem versátil e fácil de usar, com “baterias incluídas”.

Quando olhamos pet_sdk pela primeira vez através de nossos óculos “TurboPython”, o que realmente queríamos era algo como Julia. Vamos reescrever o Snippet 2 em Julia, usando sua homoiconicidade e algumas das outras ferramentas de metaprogramação que ele oferece:

 using pet_sdk for (pet, care_fn) = (("cat", :clean_litterbox), ("dog", :walk_dog), ("dog", :clean_cage)) get_pets_fn = Meta.parse("pet_sdk.get_${pet}s") @eval begin local animals = $get_pets_fn() #pet_sdk.get_cats(), pet_sdk.get_dogs(), etc. for animal in animals animal.$care_fn # animal.clean_litterbox(), animal.walk_dog(), etc. end end end
Trecho 17: O poder das macros de Julia — fazendo pet_sdk funcionar para nós

Vamos detalhar o snippet 17:

  1. Nós iteramos através de três tuplas. A primeira delas é ("cat", :clean_litterbox) , então a variável pet é atribuída a "cat" e a variável care_fn é atribuída ao símbolo entre aspas :clean_litterbox .
  2. Usamos a função Meta.parse para converter uma string em uma Expression , para que possamos avaliá-la como código. Nesse caso, queremos usar o poder da interpolação de strings, onde podemos colocar uma string em outra, para definir qual função chamar.
  3. Usamos a função eval para executar o código que estamos gerando. @eval begin… end é outra forma de escrever eval(...) para evitar a redigitação do código. Dentro do bloco @eval está o código que estamos gerando dinamicamente e executando.

O sistema de metaprogramação de Julia realmente nos liberta para expressar o que queremos da maneira que queremos. Poderíamos ter usado várias outras abordagens, incluindo reflexão (como Python no Snippet 5). Também poderíamos ter escrito uma função macro que gera explicitamente o código para um animal específico, ou poderíamos ter gerado o código inteiro como uma string e usado Meta.parse ou qualquer combinação desses métodos.

Além de Julia: outros sistemas modernos de metaprogramação

Julia é talvez um dos exemplos mais interessantes e convincentes de um sistema macro moderno, mas não é, de forma alguma, o único. Rust também foi fundamental para trazer macros à frente dos programadores mais uma vez.

Em Rust, as macros são muito mais centralizadas do que em Julia, embora não vamos explorar isso completamente aqui. Por vários motivos, você não pode escrever Rust idiomático sem usar macros. Em Julia, no entanto, você pode optar por ignorar completamente a homoiconicidade e o sistema macro.

Como consequência direta dessa centralidade, o ecossistema Rust realmente abraçou as macros. Os membros da comunidade construíram algumas bibliotecas incríveis, provas de conceito e recursos com macros, incluindo ferramentas que podem serializar e desserializar dados, gerar SQL automaticamente ou até mesmo converter anotações deixadas no código para outra linguagem de programação, tudo gerado em código em tempo de compilação.

Embora a metaprogramação de Julia possa ser mais expressiva e livre, Rust é provavelmente o melhor exemplo de uma linguagem moderna que eleva a metaprogramação, já que é bastante apresentada em toda a linguagem.

De olho no futuro

Agora é um momento incrível para se interessar por linguagens de programação. Hoje, posso escrever um aplicativo em C++ e executá-lo em um navegador da Web ou escrever um aplicativo em JavaScript para ser executado em um desktop ou telefone. As barreiras à entrada nunca foram tão baixas e os novos programadores têm informações na ponta dos dedos como nunca antes.

Neste mundo de escolha e liberdade do programador, temos cada vez mais o privilégio de usar linguagens ricas e modernas, que escolhem recursos e conceitos da história da ciência da computação e linguagens de programação anteriores. É empolgante ver macros apanhadas e espanadas nesta onda de desenvolvimento. Mal posso esperar para ver o que os desenvolvedores de uma nova geração farão quando Rust e Julia os apresentarem às macros. Lembre-se, “codificar como dados” é mais do que apenas um bordão. É uma ideologia central a ter em mente ao discutir metaprogramação em qualquer comunidade online ou ambiente acadêmico.

'Codificar como dados' é mais do que apenas um bordão.

Tweet

A história de 64 anos da metaprogramação tem sido fundamental para o desenvolvimento da programação como a conhecemos hoje. Embora as inovações e a história que exploramos sejam apenas um canto da saga da metaprogramação, elas ilustram o poder e a utilidade robustos da metaprogramação moderna.