Код написания кода: введение в теорию и практику современного метапрограммирования

Опубликовано: 2022-07-22

Всякий раз, когда я думаю о том, как лучше всего объяснить макросы, я вспоминаю программу на Python, которую написал, когда впервые начал программировать. Я не мог организовать это так, как хотел. Пришлось вызывать несколько немного отличающихся функций, и код стал громоздким. То, что я искал — хотя тогда я этого не знал, — было метапрограммированием .

метапрограммирование (существительное)

Любой метод, с помощью которого программа может обрабатывать код как данные.

Мы можем построить пример, который демонстрирует те же проблемы, с которыми я столкнулся в своем проекте Python, представляя, что мы создаем серверную часть приложения для владельцев домашних животных. Используя инструменты из библиотеки pet_sdk , мы пишем Python, чтобы помочь владельцам домашних животных покупать корм для кошек:

 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)
Фрагмент 1: Заказ корма для кошек

Убедившись, что код работает, мы переходим к реализации той же логики еще для двух видов домашних животных (птиц и собак). Мы также добавили функцию записи на прием к ветеринару:

 # 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)
Фрагмент 2: Закажите корм для кошек, собак и птиц; Записаться на прием к ветеринару

Было бы неплохо объединить повторяющуюся логику Фрагмента 2 в цикл, поэтому мы решили переписать код. Мы быстро понимаем, что поскольку каждая функция называется по-разному, мы не можем определить, какую из них (например, book_bird_appointment , book_cat_appointment ) вызывать в нашем цикле:

 import pet_sdk all_animals = pet_sdk.get_birds() + pet_sdk.get_cats() + pet_sdk.get_dogs() for animal in all_animals: # What now?
Фрагмент 3: Что теперь?

Давайте представим версию Python с турбонаддувом, в которой мы можем писать программы, автоматически генерирующие окончательный код, который нам нужен, — такую, в которой мы можем гибко, легко и плавно манипулировать нашей программой, как если бы это был список, данные в файле или что-то еще. другой общий тип данных или ввод программы:

 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)
Фрагмент 4: TurboPython: воображаемая программа

Это пример макроса , доступного в таких языках, как Rust, Julia или C, но не в Python.

Этот сценарий — отличный пример того, как может быть полезно написать программу, способную изменять и манипулировать своим собственным кодом. В этом и состоит прелесть макросов, и это один из многих ответов на более важный вопрос: как мы можем заставить программу анализировать собственный код, рассматривая его как данные, а затем действовать в соответствии с этим самоанализом?

В широком смысле все методы, которые могут привести к такому самоанализу, подпадают под общий термин «метапрограммирование». Метапрограммирование — обширная область разработки языков программирования, и ее можно проследить до одной важной концепции: код как данные.

Отражение: в защиту Python

Вы могли бы указать, что, хотя Python не поддерживает макросы, он предлагает множество других способов написания этого кода. Например, здесь мы используем метод isinstance() , чтобы идентифицировать класс, экземпляром которого является наша animal переменная, и вызывать соответствующую функцию:

 # 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)
Фрагмент 5: Идиоматический пример

Мы называем этот тип рефлексии метапрограммирования и вернемся к нему позже. Код фрагмента 5 по-прежнему немного громоздкий, но для программиста его легче написать, чем код фрагмента 2, в котором мы повторили логику для каждого перечисленного животного.

Вызов

Используя метод getattr , измените предыдущий код, чтобы он вызывал соответствующие функции order_*_food и book_*_appointment динамически. Возможно, это делает код менее читаемым, но если вы хорошо знаете Python, стоит подумать о том, как вы могли бы использовать getattr вместо функции isinstance и упростить код.


Гомоиконность: важность Лиспа

Некоторые языки программирования, такие как Lisp, выводят концепцию метапрограммирования на другой уровень с помощью гомоиконичности .

гомоиконичность (существительное)

Свойство языка программирования, при котором нет различия между кодом и данными, с которыми работает программа.

Lisp, созданный в 1958 году, является старейшим гомоиконичным языком и вторым по возрасту языком программирования высокого уровня. Получив свое название от процессора LISt, Lisp стал революцией в вычислительной технике, которая глубоко повлияла на то, как используются и программируются компьютеры. Трудно переоценить то, насколько фундаментально и характерно влияние Lisp на программирование.

Emacs написан на Лиспе, единственном красивом компьютерном языке. Нил Стивенсон

Лисп был создан всего через год после Фортрана, в эпоху перфокарт и военных компьютеров, заполнявших комнату. Тем не менее, программисты до сих пор используют Лисп для написания новых современных приложений. Первый создатель Lisp, Джон Маккарти, был пионером в области искусственного интеллекта. В течение многих лет Lisp был языком ИИ, и исследователи ценили возможность динамически переписывать собственный код. Сегодняшние исследования ИИ сосредоточены вокруг нейронных сетей и сложных статистических моделей, а не такого типа кода генерации логики. Однако исследования, проведенные в области ИИ с использованием Lisp, особенно исследования, проведенные в 60-х и 70-х годах в Массачусетском технологическом институте и Стэнфорде, создали область, которую мы знаем, и ее огромное влияние продолжается.

Появление Lisp впервые открыло ранним программистам практические вычислительные возможности таких вещей, как рекурсия, функции высшего порядка и связанные списки. Он также продемонстрировал мощь языка программирования, построенного на идеях лямбда-исчисления.

Эти идеи вызвали взрыв в разработке языков программирования и, как выразился Эдсгер Дейкстра, одно из величайших имен в компьютерной науке, « … помогли ряду наших самых одаренных собратьев придумать ранее невозможные мысли».

В этом примере показана простая программа на Лиспе (и ее эквивалент в более знакомом синтаксисе Python), которая определяет функцию «факториал», которая рекурсивно вычисляет факториал своих входных данных и вызывает эту функцию с входными данными «7»:

Лисп питон
( 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 ))

Код как данные

Несмотря на то, что это одно из самых впечатляющих и последовательных нововведений Лиспа, гомоиконичность, в отличие от рекурсии и многих других концепций, впервые появившихся в Лиспе, не вошла в большинство современных языков программирования.

В следующей таблице сравниваются гомоиконные функции, которые возвращают код как в Julia, так и в Lisp. Julia — это гомоиконичный язык, который во многих отношениях напоминает языки высокого уровня, с которыми вы, возможно, знакомы (например, Python, Ruby).

Ключевой частью синтаксиса в каждом примере является символ кавычек . Джулия использует : (двоеточие) для цитирования, а Лисп использует ' (одинарную кавычку):

Юлия Лисп
function function_that_returns_code() return :(x + 1 ) end
 ( defun function_that_returns_code () '(+ x 1 ))

В обоих примерах кавычка рядом с основным выражением ( (x + 1) или (+ x 1) ) преобразует его из кода, который должен был быть вычислен напрямую, в абстрактное выражение, которым мы можем манипулировать. Функция возвращает код, а не строку или данные. Если бы мы вызвали нашу функцию и написали бы print(function_that_returns_code()) , Джулия напечатала бы код, представленный в виде строки x+1 (и эквивалент верен для Lisp). И наоборот, без : (или ' в Лиспе) мы получили бы ошибку, что x не определен.

Вернемся к нашему примеру с Джулией и расширим его:

 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
Фрагмент 6: Расширенный пример Джулии

Функцию eval можно использовать для запуска кода, который мы генерируем из другого места в программе. Обратите внимание, что распечатываемое значение основано на определении переменной x . Если бы мы попытались eval наш сгенерированный код в контексте, где x не был определен, мы бы получили ошибку.

Гомоиконичность — это мощный вид метапрограммирования, способный открыть новые и сложные парадигмы программирования, в которых программы могут адаптироваться на лету, генерируя код, соответствующий проблемам, специфичным для предметной области, или новым встречающимся форматам данных.

Возьмем, к примеру, WolframAlpha, где одноименный язык Wolfram Language может генерировать код, адаптирующийся к невероятному кругу задач. Вы можете спросить WolframAlpha: «Каков ВВП Нью-Йорка, разделенный на население Андорры?» и, что примечательно, получить логичный ответ.

Кажется маловероятным, что кому-то когда-либо придет в голову включить эти неясные и бессмысленные вычисления в базу данных, но Wolfram использует метапрограммирование и онтологический граф знаний для написания кода «на лету», чтобы ответить на этот вопрос.

Важно понимать гибкость и мощь, которые обеспечивают Lisp и другие гомоиконичные языки. Прежде чем мы углубимся дальше, давайте рассмотрим некоторые из имеющихся в вашем распоряжении вариантов метапрограммирования:

Определение Примеры Заметки
гомоиконичность Характеристика языка, в которой код является данными «первого класса». Поскольку между кодом и данными нет разделения, их можно использовать взаимозаменяемо.
  • Лисп
  • Пролог
  • Юлия
  • Ребол/Красный
  • Вольфрам Язык
Здесь Lisp включает в себя другие языки семейства Lisp, такие как Scheme, Racket и Clojure.
Макросы Оператор, функция или выражение, которые принимают код в качестве входных данных и возвращают код в качестве выходных данных.
  • macro_rules! , Derive и процедурные макросы
  • Вызовы Джулии @macro
  • Лисп defmacro
  • C #define
(См. следующее примечание о макросах C.)
Директивы препроцессора (или прекомпилятора) Система, которая принимает программу в качестве входных данных и на основе утверждений, включенных в код, возвращает измененную версию программы в качестве выходных данных.
  • Макросы C
  • Система препроцессора C++ #
Макросы C реализованы с использованием системы препроцессора C, но это два разных понятия.

Ключевое концептуальное различие между макросами C (в которых мы используем директиву препроцессора #define ) и другими формами директив препроцессора C (например, #if и #ifndef ) заключается в том, что мы используем макросы для генерации кода, используя другие, не относящиеся к #define , директивы. директивы препроцессора для условной компиляции другого кода. Они тесно связаны в C и в некоторых других языках, но это разные типы метапрограммирования.
Отражение Способность программы исследовать, модифицировать и анализировать собственный код.
  • getattr isinstance функции
  • JavaScript Reflect и typeof
  • Java getDeclaredMethods
  • Иерархия классов System.Type в .NET
Отражение может происходить во время компиляции или во время выполнения.
Дженерики Возможность писать код, который действителен для ряда различных типов или может использоваться в нескольких контекстах, но храниться в одном месте. Мы можем определить контексты, в которых код действителен явно или неявно.

Дженерики в стиле шаблона:

  • С++
  • Ржавчина
  • Ява

Параметрический полиморфизм:

  • Хаскелл
  • МЛ
Общее программирование — более широкая тема, чем общее метапрограммирование, и грань между ними четко не определена.

С точки зрения этого автора, система параметрических типов считается метапрограммированием только в том случае, если она написана на языке со статической типизацией.
Справочник по метапрограммированию

Давайте рассмотрим несколько практических примеров гомоиконичности, макросов, директив препроцессора, рефлексии и дженериков, написанных на разных языках программирования:

 # 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)
Фрагмент 7: гомоиконичность в Юлии
 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; }
Фрагмент 8: Директивы препроцессора в 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)}")
Фрагмент 9: отражение в 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); } }
Фрагмент 10: Обобщения в 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); }
Фрагмент 11: Макросы в Rust

Макросы (например, во фрагменте 11) снова становятся популярными в языках программирования нового поколения. Чтобы успешно развивать их, мы должны рассмотреть ключевую тему: гигиена.

Гигиенические и негигиеничные макросы

Что значит для кода быть «гигиеничным» или «негигиеничным»? Чтобы прояснить, давайте посмотрим на макрос Rust, созданный с помощью macro_rules! функция. Как следует из названия, macro_rules! генерирует код на основе правил, которые мы определяем. В этом случае мы назвали наш макрос my_macro , а правило — «Создать строку кода, let x = $n », где n — наш ввод:

 macro_rules! my_macro { ($n) => { let x = $n; } } fn main() { let x = 5; my_macro!(3); println!("{}", x); }
Фрагмент 12: Гигиена в Rust

Когда мы расширяем наш макрос (запуская макрос для замены его вызова кодом, который он генерирует), мы ожидаем получить следующее:

 fn main() { let x = 5; let x = 3; // This is what my_macro!(3) expanded into println!("{}", x); }
Фрагмент 13: Наш пример, развернутый

По-видимому, наш макрос переопределил переменную x так, чтобы она была равна 3, поэтому мы можем разумно ожидать, что программа напечатает 3 . Фактически, он печатает 5 ! Удивлен? В Rust macro_rules! является гигиеничным по отношению к идентификаторам, поэтому он не будет «захватывать» идентификаторы за пределами своей области. В данном случае идентификатор был x . Если бы он был захвачен макросом, он был бы равен 3.

гигиена (существительное)

Свойство, гарантирующее, что раскрытие макроса не захватит идентификаторы или другие состояния вне области действия макроса. Макросы и макросистемы, не обеспечивающие этого свойства, называются негигиеничными .

Гигиена в макросах — довольно спорная тема среди разработчиков. Сторонники настаивают на том, что без гигиены слишком легко случайно незаметно изменить поведение вашего кода. Представьте себе макрос, значительно более сложный, чем фрагмент 13, используемый в сложном коде с множеством переменных и других идентификаторов. Что, если бы этот макрос использовал одну из тех же переменных, что и ваш код, а вы этого не заметили?

Разработчик нередко использует макрос из внешней библиотеки, не прочитав исходный код. Это особенно распространено в новых языках, которые предлагают поддержку макросов (например, Rust и 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; }
Фрагмент 14: Злой макрос C

Этот негигиеничный макрос на языке C захватывает идентификатор веб- website и меняет его значение. Конечно, захват идентификатора не является вредоносным. Это просто случайное следствие использования макросов.

Итак, гигиеничные макросы — это хорошо, а негигиеничные — плохо, верно? К сожалению, это не так просто. Есть веские основания утверждать, что гигиенические макросы ограничивают нас. Иногда полезно использовать захват идентификатора. Давайте вернемся к фрагменту 2, где мы используем pet_sdk для предоставления услуг трем видам домашних животных. Наш исходный код начинался так:

 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…
Фрагмент 15: Обратно к ветеринару — вспоминаем pet sdk

Вы помните, что Фрагмент 3 был попыткой объединить повторяющуюся логику Фрагмента 2 во всеобъемлющий цикл. Но что, если наш код зависит от идентификаторов cats и dogs , а мы хотели написать что-то вроде следующего:

 {animal}s = pet_sdk.get{animal}s() for {animal} in {animal}s: # {animal} specific code
Фрагмент 16: Полезный захват идентификатора (в воображаемом «TurboPython»)

Фрагмент 16, конечно, немного прост, но представьте себе случай, когда мы хотели бы, чтобы макрос написал 100% данной части кода. Гигиенические макросы могут быть ограничивающими в таком случае.

Хотя дебаты о гигиеничности и негигиеничности макроэкономических показателей могут быть сложными, хорошая новость заключается в том, что вам не нужно занимать определенную позицию. Язык, который вы используете, определяет, будут ли ваши макросы гигиеничными или негигиеничными, поэтому имейте это в виду при использовании макросов.

Современные макросы

У макросов сейчас немного времени. Долгое время акцент современных императивных языков программирования смещался от макросов как основной части их функциональности, избегая их в пользу других типов метапрограммирования.

Языки, которым обучали новых программистов в школах (например, Python и Java), говорили им, что все, что им нужно, — это рефлексия и дженерики.

Со временем, когда эти современные языки стали популярными, макросы стали ассоциироваться с устрашающим синтаксисом препроцессоров C и C++ — если программисты вообще знали о них.

Однако с появлением Rust и Julia тенденция вернулась к макросам. Rust и Julia — это два современных, доступных и широко используемых языка, которые переопределили и популяризировали концепцию макросов с некоторыми новыми и инновационными идеями. Это особенно интересно в Julia, который, похоже, готов занять место Python и R в качестве простого в использовании универсального языка «в комплекте с батарейками».

Когда мы впервые посмотрели на pet_sdk через наши очки «TurboPython», нам действительно хотелось чего-то вроде Джулии. Давайте перепишем фрагмент 2 на языке Julia, используя его гомоиконичность и некоторые другие инструменты метапрограммирования, которые он предлагает:

 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
Фрагмент 17: Сила макросов Джулии — pet_sdk работать на нас

Давайте разберем фрагмент 17:

  1. Мы перебираем три кортежа. Первым из них является ("cat", :clean_litterbox) , поэтому переменная pet присваивается "cat" , а переменная care_fn присваивается символу в кавычках :clean_litterbox .
  2. Мы используем функцию Meta.parse для преобразования строки в Expression , чтобы мы могли оценить ее как код. В этом случае мы хотим использовать силу интерполяции строк, когда мы можем поместить одну строку в другую, чтобы определить, какую функцию вызывать.
  3. Мы используем функцию eval для запуска кода, который мы генерируем. @eval begin… end — это еще один способ написания eval(...) , позволяющий избежать повторного ввода кода. Внутри блока @eval находится код, который мы динамически генерируем и выполняем.

Система метапрограммирования Джулии действительно позволяет нам выражать то, что мы хотим, так, как мы этого хотим. Мы могли бы использовать несколько других подходов, включая отражение (например, Python во фрагменте 5). Мы также могли бы написать макрофункцию, которая явно генерирует код для конкретного животного, или мы могли бы сгенерировать весь код в виде строки и использовать Meta.parse или любую комбинацию этих методов.

Помимо Джулии: другие современные системы метапрограммирования

Джулия, пожалуй, один из самых интересных и убедительных примеров современной макросистемы, но ни в коем случае не единственный. Rust также сыграл важную роль в том, что макросы снова стали доступны программистам.

В Rust макросы имеют гораздо более централизованное значение, чем в Julia, хотя мы не будем подробно исследовать это здесь. По ряду причин вы не можете писать идиоматический Rust без использования макросов. Однако в Julia вы можете полностью игнорировать гомоиконичность и макросистему.

Как прямое следствие этой центральности, экосистема Rust действительно включает макросы. Члены сообщества создали несколько невероятно крутых библиотек, доказательств концепции и функций с помощью макросов, включая инструменты, которые могут сериализовать и десериализовать данные, автоматически генерировать SQL или даже преобразовывать аннотации, оставшиеся в коде, в другой язык программирования. время компиляции.

В то время как метапрограммирование Джулии может быть более выразительным и свободным, Rust, вероятно, является лучшим примером современного языка, который поднимает метапрограммирование на новый уровень, поскольку он широко используется во всем языке.

Взгляд в будущее

Сейчас невероятное время, чтобы интересоваться языками программирования. Сегодня я могу написать приложение на C++ и запустить его в веб-браузере или написать приложение на JavaScript для запуска на компьютере или телефоне. Барьеры для входа никогда не были ниже, и новые программисты имеют доступ к информации, как никогда раньше.

В этом мире выбора и свободы программиста у нас все чаще появляется возможность использовать богатые современные языки, в которых тщательно отобраны функции и концепции из истории компьютерных наук и более ранних языков программирования. Приятно видеть, как макросы поднимаются и стираются на этой волне разработки. Мне не терпится увидеть, что сделают разработчики нового поколения, когда Rust и Julia познакомят их с макросами. Помните, что «код как данные» — это больше, чем просто крылатая фраза. Это основная идеология, о которой следует помнить при обсуждении метапрограммирования в любом онлайн-сообществе или академической среде.

«Код как данные» — это больше, чем просто крылатая фраза.

Твитнуть

64-летняя история метапрограммирования стала неотъемлемой частью развития программирования в том виде, в каком мы его знаем сегодня. Хотя инновации и история, которые мы исследовали, являются лишь частью саги о метапрограммировании, они иллюстрируют мощную силу и полезность современного метапрограммирования.