<p>I believe the main purpose of both multi-stage programming (à la <a href="https://okmij.org/ftp/ML/MetaOCaml.html" rel="nofollow noreferrer">MetaOcaml</a> and <a href="https://github.com/stedolan/ppx_stage" rel="nofollow noreferrer">ppx_stage</a>) (AKA staged metaprogramming) and monomorphization (à la <a href="https://www.rwx.com/blog/how-roc-compiles-closures" rel="nofollow noreferrer">Roc</a> and <a href="https://rustc-dev-guide.rust-lang.org/backend/monomorph.html" rel="nofollow noreferrer">Rust</a>) is to remove some amount of indirection from compiled code - especially of calls to higher-order functions - although by fairly different approaches, with their own ramifications on a language's design and implementation.</p>
<p>Is my understanding correct? Are there cases where it would provide any advantage to have both?</p>
<p><strong>EDIT</strong>: after looking around a bit more, I think by &quot;monomorphization&quot; I'm thinking mainly of &quot;defunctionalization&quot; (which Rust and Roc do as part of monomorphization). So maybe a better question would be: if a language already does defunctionalization, are there any further benefits to be had by also supporting MSP?</p>

<language-design><design-choice-comparison><monomorphization>
Multi-stage programming vs monomorphization/defunctionalization
<p>This seems like an apples to oranges comparison. Since we're mentioning Rust, staged metaprogramming is closer in kind to macros. Manifestly, Rust has macros and it uses monomorphization. There is nothing surprising about that because they are quite unrelated things.</p>
<p>There doesn't seem to be anything specific to staged metaprogramming that makes it especially relevant to this question. Any metaprogramming facility enables users to ensure guarantees on performance by controlling the shape of generated code.</p>
<p>It's surprising to suggest that monomorphization or defunctionalization seems to subsume staged metaprogramming. While monomorphization does remove indirection, it only removes a specific kind of indirection. Staged metaprogramming or really any form of metaprogramming is not a specific technique but a framework in which users can design solutions tailored to their individual situations, so you could in principle use metaprogramming to do the equivalent of monomorphization, but also many more optimizations.</p>
<p>Monomorphization and defunctionalization are compilation techniques: they translate a source language that has certain features (polymorphism, traits, higher-order functions) into a target language that doesn't have those features. Monomorphization exists because language designers decided they want polymorphism in their language. Performance is a secondary concern, which gives preference to monomorphization over a uniform representation of values in Haskell for example (it's worth noting that Haskell also does monomorphization as an optimization, but it's heuristic-driven, not systematic).</p>
<p>Defunctionalization actually creates indirection, since it replaces lambdas with closures, and the code of the lambda is indirectly represented by a tag or a function pointer. Closures are inherently about indirection as a way to compile a high-level language with lambdas to a low-level language without lambdas. The actually beneficial idea in the linked blog post about Roc is &quot;lambda sets&quot;, which is to keep track of the position of individual lambdas as part of function types, and you reap benefits from it by following it with monomorphization, which Roc does as described in that blog post.</p>
<p>A more conceptual question is the apparently overlapping roles of compilation and metaprogramming, as they are both about &quot;generating code&quot;. A compiler is just a metaprogram. A metaprogram is just a compiler. To a certain extent, it's all a difference in perspective. But perspective is everything. It's how we think about programming; changing the perspective changes what ideas are worth considering. Furthermore, there is room for both metaprogramming and compilers to coexist. Metaprogramming without compilers is just writing assembly code to generate assembly code. When there are no compilers, everybody becomes a compiler writer. Metaprogramming really takes on a life of its own among high-level languages. Conversely, there are only so many code transformations and optimizations you can fit in a single compiler. Metaprogramming puts the power of compiler writers and language designers into the hands of language users.</p>

CC BY-SA 4.0
2024-08-08T11:24:13.343Z
2024-08-08T11:24:13.343Z
Li-yao Xia
CC BY-SA 4.0
2024-08-08T13:23:12.573Z
Thanks very much for the great answer!

I agree that its an apples to oranges comparison. But my feeling is that the primary usefulness of MSP is that it provides a mechanism for "defunctionalization by hand". For example, [this version of List.map from ppx_stage](https://github.com/stedolan/ppx_stage/blob/0b8494d31f8913945e5f69e61b443457fe48028e/README.md#binding) seems to "stub out" the same version of `map plus1` that a defunctionalizing compiler would.

Perhaps this is an example of your note about the overlapping roles of compilation and metaprogramming.
doesntgolf
CC BY-SA 4.0
2024-08-08T16:44:12.043Z
I think "MSP" is really about making metaprogramming safer and more convenient (how to guarantee that the output code will be well-formed by construction), which makes users confident enough to do advanced AST manipulations. But in principle you could do this kind of thing as long as you have a sufficiently expressive metalanguage for constructing programs (like Rust procedural macros since the implementation language is Rust itself).
Li-yao Xia
# Multi-stage programming vs monomorphization/defunctionalization
Posted by: username_0 on 2024-08-05
I believe the main purpose of both multi-stage programming (à la [MetaOcaml](https://okmij.org/ftp/ML/MetaOCaml.html) and [ppx\_stage](https://github.com/stedolan/ppx_stage)) (AKA staged metaprogramming) and monomorphization (à la [Roc](https://www.rwx.com/blog/how-roc-compiles-closures) and [Rust](https://rustc-dev-guide.rust-lang.org/backend/monomorph.html)) is to remove some amount of indirection from compiled code - especially of calls to higher-order functions - although by fairly different approaches, with their own ramifications on a language's design and implementation.

Is my understanding correct? Are there cases where it would provide any advantage to have both?

**EDIT**: after looking around a bit more, I think by "monomorphization" I'm thinking mainly of "defunctionalization" (which Rust and Roc do as part of monomorphization). So maybe a better question would be: if a language already does defunctionalization, are there any further benefits to be had by also supporting MSP?
**Tags:** language-design design-choice-comparison monomorphization
---
## 1 answers
### Answer by username_1 on 2024-08-08. Score: 4 | Accepted answer
This seems like an apples to oranges comparison. Since we're mentioning Rust, staged metaprogramming is closer in kind to macros. Manifestly, Rust has macros and it uses monomorphization. There is nothing surprising about that because they are quite unrelated things.

There doesn't seem to be anything specific to staged metaprogramming that makes it especially relevant to this question. Any metaprogramming facility enables users to ensure guarantees on performance by controlling the shape of generated code.

It's surprising to suggest that monomorphization or defunctionalization seems to subsume staged metaprogramming. While monomorphization does remove indirection, it only removes a specific kind of indirection. Staged metaprogramming or really any form of metaprogramming is not a specific technique but a framework in which users can design solutions tailored to their individual situations, so you could in principle use metaprogramming to do the equivalent of monomorphization, but also many more optimizations.

Monomorphization and defunctionalization are compilation techniques: they translate a source language that has certain features (polymorphism, traits, higher-order functions) into a target language that doesn't have those features. Monomorphization exists because language designers decided they want polymorphism in their language. Performance is a secondary concern, which gives preference to monomorphization over a uniform representation of values in Haskell for example (it's worth noting that Haskell also does monomorphization as an optimization, but it's heuristic-driven, not systematic).

Defunctionalization actually creates indirection, since it replaces lambdas with closures, and the code of the lambda is indirectly represented by a tag or a function pointer. Closures are inherently about indirection as a way to compile a high-level language with lambdas to a low-level language without lambdas. The actually beneficial idea in the linked blog post about Roc is "lambda sets", which is to keep track of the position of individual lambdas as part of function types, and you reap benefits from it by following it with monomorphization, which Roc does as described in that blog post.

A more conceptual question is the apparently overlapping roles of compilation and metaprogramming, as they are both about "generating code". A compiler is just a metaprogram. A metaprogram is just a compiler. To a certain extent, it's all a difference in perspective. But perspective is everything. It's how we think about programming; changing the perspective changes what ideas are worth considering. Furthermore, there is room for both metaprogramming and compilers to coexist. Metaprogramming without compilers is just writing assembly code to generate assembly code. When there are no compilers, everybody becomes a compiler writer. Metaprogramming really takes on a life of its own among high-level languages. Conversely, there are only so many code transformations and optimizations you can fit in a single compiler. Metaprogramming puts the power of compiler writers and language designers into the hands of language users.
> **Comments:**
> **username_0** (2024-08-08): Thanks very much for the great answer!

I agree that its an apples to oranges comparison. But my feeling is that the primary usefulness of MSP is that it provides a mechanism for "defunctionalization by hand". For example, [this version of List.map from ppx_stage](https://github.com/stedolan/ppx_stage/blob/0b8494d31f8913945e5f69e61b443457fe48028e/README.md#binding) seems to "stub out" the same version of `map plus1` that a defunctionalizing compiler would.

Perhaps this is an example of your note about the overlapping roles of compilation and metaprogramming.  
> **username_1** (2024-08-08): I think "MSP" is really about making metaprogramming safer and more convenient (how to guarantee that the output code will be well-formed by construction), which makes users confident enough to do advanced AST manipulations. But in principle you could do this kind of thing as long as you have a sufficiently expressive metalanguage for constructing programs (like Rust procedural macros since the implementation language is Rust itself).  
---
