Sitemap

Lambda Mountain v1.22 Patch Notes

Type-directed macros are a marvel

2 min readMay 24, 2025
  • new macro system based on AST subtree rewriting
  • macro helpers make macros modular
  • macros can be type-sensitive and support specialization by argument type
  • the old macro system is still hanging around until we get GC and remove all LM code
  • ported LM Core inference to LSTS
  • big improvement with lots of bugs and corner cases removed
  • improved compile times by about 50%
  • linear time unification
  • starting to stabilize several internal compiler APIs for use by anyone building a backend
  • replace old C frontend with a fully standards compliant C parser (still a work in progress)
  • fixed a bug with smart strings possibly being uninitialized before use

Type-directed macros are really fun to work with. It is still very unclear what the natural limitations are with this feature. If anyone knows of a language (research or otherwise) with a similar feature then let us know.

What is a Type-Directed Macro?

If we want to codegen two different types of for loops, but still use the same syntax then we can write:

typed macro macro::for(item: lazy, iter: List<?>, loop: lazy): lazy = (
let uuid(iter-term) = iter;
while uuid(iter-term).has-head { match uuid(iter-term) {
[item.. uuid(tl)] => (
loop; uuid(iter-term) = uuid(tl);
);
}}
);

typed macro macro::for(item: lazy, iter: Vector<?>, loop: lazy): lazy = (
let uuid(iter-term) = iter;
let uuid(iter-i) = 0_u64;
let uuid(iter-length) = uuid(iter-term).length;
while uuid(iter-i) < uuid(iter-length) { match uuid(iter-term)[uuid(iter-i)] {
item => (
loop; uuid(iter-i) = uuid(iter-i) + 1;
);
}}
);

Here we have one macro to work with Lists and another to work with Vector. This avoids the Iterator type design pitfalls and codegen complexities completely by simply never touching any Iterator type at all.

This step happens during type-checking and works by recursively rewriting AST subtrees. This means that macros can directly affect typechecking and program flow results.

Another example would be the proposed calling convention for closures. Typically there is some manual run-around with calling conventions for Arrows vs Closures. There are cross-cutting concerns here due to the interactions with type-inference, data representation selection, and finally codegen. Type-directed macros make this much simpler with a simple declaration:

typed macro macro::apply(f: Arrow<d,r>, a: lazy): lazy;
typed macro macro::apply(f: Closure<d,r>, a: lazy): lazy;

This way, the existing compiler infrastructure can help us choose the best code-paths and avoid special-case logic in the compiler.

--

--

No responses yet