Macro Recipes

Use this page for small MakrellPy macro patterns that you can adapt directly. For the broader model behind meta and macros, continue with Metaprogramming.

Start with the checked-in showcase

MakrellPy already has a compact public macro example in impl/py/examples/macros/showcase.mr.

Run it:

makrell showcase.mr

That example is worth reading because it shows three different kinds of macro:

  • pipe rewrites a sequence of forms into pipeline style

  • rpn reads postfix input and builds ordinary Makrell syntax

  • lisp hosts a Lisp-like round-bracket notation inside Makrell

If you only read one macro example first, make it that one.

Recipe: duplicate an expression

{def macro twice [x]
    [{quote $x} {quote $x}]}

{twice {print "hello"}}

This is the smallest useful macro shape:

  • receive syntax

  • return syntax

  • keep the transformation obvious

It is a good first macro because you can inspect what it receives and what it returns without much surrounding machinery.

Recipe: reshape a whole form with regular

{def macro second [ns]
    ns = {regular ns}
    {quote {unquote ns@1}}}

{second 2 3 5}

Use regular when you want the macro arguments as a more ordinary list of nodes rather than relying on raw spacing or other structure details.

This is the pattern behind many practical macros:

  • normalise the incoming nodes

  • pick out the parts you need

  • build a new expression with quote and unquote

Recipe: keep helper logic in meta

{meta
    {fun wrap_print [label expr]
        {quote
            {print {unquote label}}
            {unquote expr}}}}

{def macro announce [ns]
    ns = {regular ns}
    {wrap_print ns@0 ns@1}}

Keeping helper logic in meta is usually easier to read than packing all the logic into one large def macro body.

This split is often a good default:

  • meta for compile-time helper functions and values

  • def macro for the syntax transformation entry point

Recipe: wrap a block with setup and teardown

{def macro timeit [ns]
    [
        {quote {import time}}
        {quote start = {time.time}}
    ] + ns + [
        {quote {print "Time taken:" {time.time} - start}}
    ]}

This kind of macro is useful when you want to inject repeated structure around a user-provided block.

It is a good pattern for:

  • instrumentation

  • tracing

  • repeated setup

  • repeated cleanup

Recipe: build a tiny language, not just a shortcut

The checked-in pipe, rpn, and lisp examples are useful because they show three different macro ambitions:

  • ergonomic rewriting

  • alternative notation

  • embedded language surface

That is usually where Makrell macros become most interesting: not only saving a few characters, but making a different structural style possible.

Practical advice

When a macro becomes hard to understand:

  • shrink it to the smallest failing example

  • inspect the incoming nodes first

  • move helper logic into meta

  • keep generated structure explicit with quote

Also remember that macros can introduce bindings. Generated names may still collide with user code, so readability and restraint matter.