Writing Your First Macro in Phel
If you have played with threading macros or pattern matching, you have already been using macros without thinking about it. Now it is time to write your own.
PHP developers sometimes reach for eval() or code generation tools when they need to produce code dynamically. Clojure developers know a better way: macros. Phel brings that same power to the PHP ecosystem, letting you extend the language at compile time instead of juggling strings at runtime.
Functions vs macros: when code is data
Regular functions receive values and return values. Macros receive code and return code. The transformation happens at compile time, so there is zero runtime cost.
Say you keep writing this pattern:
You could wrap it in a function, but then logged-in? would be evaluated before the function even sees it. With a macro you create actual new syntax:
At compile time, Phel rewrites that call into an if form. Your condition stays lazy, just like the built-in control flow. Clojure folks will feel right at home; the syntax is nearly identical.
Fun fact: Phel's core already has
when-notwhich does exactly this. Peek at the source and you will find the same pattern we just wrote.
Quote and unquote: treating code as data
Two concepts make macros tick: quote and unquote.
Quote (the ' character) stops evaluation. It hands you raw code instead of running it:
# => 3
' # => (+ 1 2), just a list
Quasiquote (the backtick `) works like quote, but you can poke holes with unquote (,) to let specific pieces evaluate:
` # => (1 2 3)
Think of quasiquote as a template. Most of it stays literal; the , parts get filled in. If you have written Clojure, this is exactly what you know.
Building unless step by step
What is happening here:
defmacrodefines a macro, just likedefndefines a function.- The docstring explains what the macro does.
testandbodyreceive unevaluated code, not values.- The backtick starts a code template.
,testsplices in the test expression;,@bodysplices the body expressions inline.
When you write:
Phel transforms it at compile time to:
No runtime overhead, no string manipulation, no eval().
A practical example: timing code
Here is something you cannot do with a plain function. Say you want to measure how long a chunk of code takes. Phel's core has a time macro that does exactly this:
# Prints: Elapsed time: 142.3 msecs
The $ suffix is a convention for local bindings inside macros. It helps avoid name collisions with user code. The body runs between the two microtime calls, and you get the elapsed time printed for free.
In PHP you would wrap this in a closure or duplicate the timing code everywhere. The macro keeps it clean and reusable.
Avoiding name collisions with gensym
The $ suffix works for simple cases, but what if your macro could be nested or the user happens to use start$ as a variable? For guaranteed uniqueness, use gensym to generate fresh symbols.
Here is how Phel's core implements with-output-buffer:
# => "Hello World"
We call gensym outside the quasiquote to get a unique symbol, then unquote it with ,res wherever we need it. No matter how many times you nest with-output-buffer, each expansion gets its own symbol.
More patterns from Phel's core
Short-circuit evaluation with or:
Notice how or uses gensym because it recursively expands itself. Each level needs its own unique binding.
Auto-logging function calls:
The ',name pattern (quote then unquote) inserts the literal symbol name into the output, so the log shows the actual function name.
When to reach for a macro
Macros are powerful, but they are not always the right tool:
- Use a macro when you need to control evaluation order.
- Use a macro when you want new syntax (like
timeorwith-output-buffer). - Use a macro to eliminate boilerplate at compile time.
- Use a function for everything else.
Functions are easier to debug, compose, and pass around. If a function can do the job, stick with it. Reach for macros when functions hit their limits.
Debugging with macroexpand
When a macro misbehaves, macroexpand shows you the generated code without running it:
# => (if (> x 10) nil (do (print "small")))
Paste in your macro call, see what comes out. It takes the mystery out of debugging.
Go build something
You now have the same metaprogramming tools that make Lisp so flexible. Start small: spot a pattern you repeat often and wrap it in a macro. Check the macro documentation when you want to dig deeper.
Once you get comfortable, explore Phel's core. Even defn is just a macro that expands to def plus fn.
The -> and ->> threading macros, cond, case, for — they are all built with the same primitives you just learned. That is the Lisp way, and now it runs on PHP.