Stream: ideas

Topic: metaprograming


view this post on Zulip Zeljko Nesic (Mar 30 2022 at 00:22):

Dear Santa,

What I really want for xmas is to have a platform that will take Roc code, which would have to produce another Roc AST which would in turn get compiled again and turned into executable.

Write Roc code that produces more roc code. Big important caveat, I don't want to generated code communicate with parent code. Just based on some Roc.roc to produce more projects and Roc expressions and types, which should be type checked on its own right.

view this post on Zulip Kevin Gillette (Mar 30 2022 at 06:05):

When you say you don't want it to communicate with parent code, what does that mean, specifically (what would be the mechanism of that communication), and why would such communication be a problem?

view this post on Zulip Brian Carroll (Mar 30 2022 at 11:20):

I would assume it means the generated code being able to refer to variables from wherever it was generated? Macros in Rust can do this for example.

view this post on Zulip Brian Carroll (Mar 30 2022 at 11:20):

But if you don't have that then it's basically generating strings to dump to a file.

view this post on Zulip Brian Carroll (Mar 30 2022 at 11:21):

So what kind of support would help with that @Zeljko Nesic ?

view this post on Zulip Brian Carroll (Mar 30 2022 at 11:22):

Wonder if it's more a feature of the build system than the compiler.

view this post on Zulip Zeljko Nesic (Mar 30 2022 at 14:10):

My initial impulse was to have something similar to Haskell's Servant library, but without type-level magic.

One way to being able to archive this to take some definitions, and build out concrete types of the API.

@Brian Carroll You are probably right, this should be just setting some circular pipes in the build system, but still I wonder, maybe there is a better way. That is why I was asking Santa for help. :)

view this post on Zulip Brian Carroll (Mar 30 2022 at 14:23):

Right. I think the "circular" issue could be dealt with by doing it in passes.
Run a compilation pass. If it generated more Roc code, do another pass. If not, we are finished compiling.
After compiling, run the linker on all the object files from all the passes.

view this post on Zulip Brian Carroll (Mar 30 2022 at 14:25):

Maybe there's also a case for generating something more structured than text. Or a builder library for code strings.

view this post on Zulip Kevin Gillette (Mar 30 2022 at 14:33):

What would be the practical use case for multiple rounds of code generation? Allowing more than one round would permit compilation to never end, unless we set a limit on the number of rounds, but a limit other than one feels arbitrary to me (3? is that enough? 10? is the programmer doing the right thing if they need 10?)

Would such generation be deterministic and cacheable, such that the IR or result of, possibly multiple stages of generation, do not need to be reevaluated on subsequent builds if the input hasn't changed, and such that multiple distinct compilation phases, including this kind, can be cached together?

view this post on Zulip Brian Carroll (Mar 30 2022 at 18:25):

No idea, I had no practical purposes in mind, just thinking about how to implement it and handling edge cases out of habit.

view this post on Zulip Zeljko Nesic (Mar 31 2022 at 01:31):

But even one level of code generation would be huge!

Why I think this should be actually "compilers" thing, because it would be really sweet if from the first pass compiler would tell you:
dude, this Roc code that I am about to generate doesn't make sense. And then points to offending ~area~ of code.

view this post on Zulip Zeljko Nesic (Mar 31 2022 at 01:32):

This also will be step closer to our vision of :octopus: :working_on_it: :tada: the editor :tada: :working_on_it: :octopus:

view this post on Zulip Lawrence Job (Jul 09 2022 at 20:12):

Apologies for awakening an old thread — I think this is a really wise and prudent idea and I’d love to expand it to other kinds of analyser in the tool chain.

Fairly recently, the .Net team rolled out Source Generators which have a few really wise traits that are worth considering:

1) they are part of the compiler/analyzer chain so the integrate nicely
2) they only allow for one pass (deterministic and cacheable), and they document (I’ll try to dig out the link) exactly why that is
3) they run in real-time in their IDE (Visual Studio) as you code
4) they can never see output from other analysers/deterministic generators (otherwise they’d have ordered dependencies andor not be deterministic; either way DX mess)

What they don’t do, which surprises me and I have t found a justification for:
1) support real time code generation in other IDEs (even their own VS Code)
2) support outputting to a normalised AST (they generate strings, which are passed to the next tool in the chain) —- fortunately they do have access to the syntax tree to analyse the user code

The best example I’ve seen is code which turns a (checked in) openapi.json file and generates types and a client in the IDE as “real” code which can be stepped through and inspected as if it were written.

The same goes with analysers — source generators and analysers come straight from the package manager and integrate into the compiler with no configuration, which is something Roc could excel at.

Aside: I think deciding between IDE analysers vs tool chain analysers is going to be contentious and nuanced for the same reason that writing a language server for external IDEs is discouraged


Last updated: Jun 16 2026 at 16:19 UTC