Все хорошие ответы до сих пор.Я хотел бы подчеркнуть, что ключевое практическое различие между макросом и функцией заключается в том, что макрос может контролировать, когда и если его аргументы вычисляются, а функция - нет.Помимо прочего, это позволяет вам писать новые управляющие конструкции на языке, которые вы не можете делать с функциями.
Например, посмотрите на макрос if-not изClojure ядро.Это похоже на стандартное «если», но инвертировано: если условие ложно, выполняется первый бит кода;если условие истинно, выполняется второе.
Если вы напишите что-то похожее как функцию, оно не будет работать.Версия функции будет выполнять код «then» и «else» сразу после вызова функции, независимо от того, является ли условие истинным или ложным.Макро-версия, с другой стороны, может выбирать, запускать ли (и когда) код, указанный в качестве аргументов.
Менее тривиальным примером является абстрагирование типичного "try ... catch".... наконец, управляющая конструкция вездесущая в Java и распространенная в других языках в макрос «с чем угодно».Если вы держите конечный ресурс, такой как дескриптор файла или сетевой сокет, который должен быть освобожден, даже в случае ошибки вам необходим блок "finally", чтобы сделать это, но код вашего приложения должен быть вставлен без оценки ввнутренняя часть блока "try", который будет запущен в этом контексте.
Вы копируете по существу одну и ту же неизменную шаблонную строку try ... catch ... finally везде в вашей программе и вставляете небольшой локальный фрагментприменимый раздел в части "попробовать".(Посмотрите на любой нетривиальный исходный код Java.) Этот шаблон не может быть абстрагирован в функцию, потому что функция будет оценивать локальный код сразу при вызове в контексте вызывающего, а затем выдавать результат этого кода в функцию «с чем угодно».
С другой стороны, макрос может задерживать оценку до тех пор, пока он не будет специально вызван кодом макроса, что позволяет ему разделить ваш произвольный локальный код наИнтерьер конструкции «попробуй» неоценен.Весь результат (весь try / catch / finally, включая ваш неоцененный специфичный для приложения код в попытке) затем возвращается вызывающей стороне и вставляется в контекст вызывающей стороны, завершается, чтобы быть там выполненным.
Таким образом, вы можете написать программу, которая пишет программы.