In this particular example, there's little to none: truth be told, I don't like WITH-FOO as a good example of why macros are fun. That's because WITH-FOO macros are commonly implemented/implementable as simple wrappers around CALL-WITH-FOO functions.
For instance, if we had a CALL-WITH-OPEN-FILE function (it's not too hard to write one yourself!), the following two syntaxes - one macro, one functional - would be equivalent:
Notice that both of these accept a pathname, additional arguments passed to OPEN, and code to be called with the opened stream. The only differences are that the functional variant needs to have its code forms wrapped in an anonymous function and that the function object passed to it is replaceable (since it's just a value).
---------------
For a more serious example, try thinking of how to implement something like CL:LOOP (a complex iteration construct) without a macro system.
Sure, LOOP is a very complex macro. But my point was that most macros in real codebases are these simple boilerplate wrappers that help readability.
I don't like codebases too full of DSLs, necessarily.
A less trivial example is defining different types of subroutine. In StumpWM, a tiling WM written in common lisp, there is the concept of commands. They're functions, but they executed as a string. "command arg1 arg2". And these strings can be bound to keys. But args might be numbers, windows, frames, strings etc.
Commands are defined through a defcommand macro. It takes types! And there's a macro for defining arbitrary types and how to parse them from a string. A command is actually a function under the hood, with a bunch of boilerplate to: parse the arguments, stick the name in a hash table, call pre- and post-command hooks, set some dynamic bindings. and so on. Defcommand abstracts this away and you can just write it just like a normal Lisp function except for the types.
For instance, if we had a CALL-WITH-OPEN-FILE function (it's not too hard to write one yourself!), the following two syntaxes - one macro, one functional - would be equivalent:
Notice that both of these accept a pathname, additional arguments passed to OPEN, and code to be called with the opened stream. The only differences are that the functional variant needs to have its code forms wrapped in an anonymous function and that the function object passed to it is replaceable (since it's just a value).---------------
For a more serious example, try thinking of how to implement something like CL:LOOP (a complex iteration construct) without a macro system.