Думка, яка суперечить: гомоконічність Ліспа - це набагато не корисна річ, ніж більшість шанувальників Lisp вважають, що ви вірите.
Щоб зрозуміти синтаксичні макроси, важливо зрозуміти компілятори. Завдання компілятора - перетворити читаний людиною код у виконуваний код. З точки зору дуже високого рівня, це має дві загальні фази: аналіз та генерація коду .
Парсинг - це процес зчитування коду, його інтерпретація згідно з набором формальних правил та перетворення його в структуру дерева, загалом відому як AST (абстрактне синтаксичне дерево). Незважаючи на все різноманіття мов програмування, це одна надзвичайна спільність: по суті, кожна мова програмування загального призначення аналізує структуру дерева.
Генерація коду приймає AST аналізатора як свій вхід і перетворює його у виконуваний код за допомогою застосування формальних правил. З точки зору ефективності, це набагато простіше завдання; багато компіляторів мови високого рівня витрачають 75% або більше свого часу на розбір.
Що слід пам’ятати про Лісп, це те, що він дуже, дуже старий. Серед мов програмування лише FORTRAN старший від Lisp. Знову назад, розбір (повільна частина складання) вважався темним таємничим мистецтвом. Оригінальні статті Джона Маккарті з теорії Ліспа (тоді, коли це була лише ідея, яку він ніколи не думав, реально реалізувати як справжню мову комп'ютерного програмування) описують дещо складніший і виразніший синтаксис, ніж сучасні "S-вирази скрізь для всього" "позначення. Це сталося пізніше, коли люди намагалися реально це здійснити. Оскільки синтаксичний розбір ще тоді не був добре зрозумілий, вони в основному карали його і скидали синтаксис у гомоніконічну структуру дерева, щоб зробити роботу аналізатора абсолютно тривіальною. Кінцевим результатом є те, що ви (розробник) повинні виконати багато аналізу " працюйте над цим, записуючи формальний AST безпосередньо у свій код. Гомоїконічність не "робить макроси набагато простішими" настільки ж, що робить писати все інше набагато складніше!
Проблема в цьому полягає в тому, що, особливо при динамічному наборі тексту, S-виразам дуже важко переносити з собою багато семантичної інформації. Коли весь ваш синтаксис є одним і тим же типом (списки списків), це не так багато в контексті, який надає синтаксис, і тому макросистема має дуже мало працювати.
Теорія компілятора пройшла довгий шлях з 1960-х років, коли був винайдений Лісп, і хоча речі, які він здійснив, були вражаючими за день, зараз вони виглядають досить примітивно. Для прикладу сучасної системи метапрограмування подивіться на (на жаль недооцінену) мову Бу. Boo є статично типовим, об'єктно-орієнтованим та відкритим кодом, тому кожен вузол AST має тип із чітко визначеною структурою, до якого розробник макрокоманду може прочитати код. Мова має відносно простий синтаксис, натхненний Python, з різними ключовими словами, які надають внутрішньосемантичного значення деревним структурам, побудованим з них, а його метапрограмування має інтуїтивний квазіцитативний синтаксис для спрощення створення нових вузлів AST.
Ось макрос, який я створив вчора, коли зрозумів, що я застосовую один і той же візерунок до купи різних місць у коді графічного інтерфейсу, де я б закликав BeginUpdate()
елемент управління інтерфейсом, здійснив оновлення в try
блоці, а потім зателефонував EndUpdate()
:
macro UIUpdate(value as Expression):
return [|
$value.BeginUpdate()
try:
$(UIUpdate.Body)
ensure:
$value.EndUpdate()
|]
macro
Команда, по суті, сам макро , один , який приймає макро тіла в якості вхідних даних і генерує клас для обробки макрокоманди. Він використовує ім'я макросу як змінної, що стоїть за MacroStatement
вузлом AST, який представляє виклик макросу. [| ... |] - квазіцитатний блок, що генерує AST, що відповідає коду всередині, а всередині блоку квазіцитату символ $ забезпечує об'єкт "unquote", заміняючи вузол, як зазначено.
За допомогою цього можна написати:
UIUpdate myComboBox:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
і поширити його на:
myComboBox.BeginUpdate()
try:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
ensure:
myComboBox.EndUpdate()
Висловити макрос таким способом простіше та інтуїтивніше, ніж це було б у макросі Lisp, оскільки розробник знає структуру MacroStatement
та знає, як працюють Arguments
і Body
властивості, і що притаманні знання можуть бути використані для вираження понять, залучених до дуже інтуїтивно зрозумілим шлях. Це також безпечніше, тому що компілятор знає структуру MacroStatement
, і якщо ви спробуєте кодувати щось, що не MacroStatement
вірно для a , компілятор впіймає це відразу і повідомляє про помилку замість вас, не знаючи, поки щось не підірветься на вас у час виконання.
Нанесення макросів на Haskell, Python, Java, Scala тощо не є складним, оскільки ці мови не гомонічні; це важко, оскільки мови не розроблені для них, і він найкраще працює, коли ієрархія AST вашої мови розроблена з самого початку, щоб її розглядали та маніпулювали макросистемою. Коли ви працюєте з мовою, розробленою з урахуванням метапрограмування з самого початку, макроси набагато простіші та простіші в роботі!