Is there any conventional way to work around mutual references in modules? I know that Racket does not support mutual references in modules, but sometimes it will happen. I mean that possibly some big module can be conceptually divided into two parts (without overlapping), so that we can write in two files. N.B. I found that racket/include
seems a good way, but I haven’t seen many people use (?). Is there any drawback of racket/include
? Another way is to extract common interfaces, but I don’t want to refactor too early… Thanks.
I sometimes collect parameter definitions and/or structure definitions in a single file. Then the other modules can require that file.
If that’s not enough (let’s say two one module A needs a value from B, but a direct require makes a circular reference) then I make a parameter holding the value. Its initial value is #f, but when module B is instantiated, it will set the parameter to the shared value.
What is the easiest way to generate a list from start
to stop
with step
?
The above approach suits me - but an alternative is to use units.
I’ve thought about using racket/include
for this too, actually. It seems like a quick low-tech way to get something working. I can imagine possible drawbacks, like whether it works okay with DrRacket background expansion and whether source locations are reported in useful way, but I haven’t tried it yet.
If racket/include
didn’t exist, making a file that’s just one big macro definition and a bunch of requires might be a similarly workable approach (and its drawbacks might be similar in nature).
Most of my ideas around this really do boil down to quoting everything in a file. There are two reasons for this that seem pretty essential:
• Only one of the files’ compilation results is going to be able to contain the compilation of the mutually recursive parts anyway, so the rest of the files need to supply their parts in quoted form so that the compiling one has code to compile. • If one file defines a syntax that should be visible in the others, then the others can’t be fully compiled independently of it. So, breaking up an N-cycle into (N – 1) modules of quoted code and 1 module that compiles them all seems like the simplest starting point, and a racket/include
approach fits the bill.
The approach I propose at https://github.com/racket/rhombus-brainstorming/issues/166\|https://github.com/racket/rhombus-brainstorming/issues/166 is basically that plus a few things to make it more seamless: The guests (quoted ones) can carry some metadata about where to find their host, so that a special require
variant can appear to import things from the guest directly (while actually following the guest’s redirect). The modules contain declarations that determine their own status as a host or a guest. The guests’ self-quoting and the hosts’ guest-fetching happen as an implicit result of those declarations, powered by #lang
-level support for the declarations.
A more sophisticated approach might cut down on some of the self-quoting, allowing certain non-mutually-recursive and non-syntax-dependent parts of files to be compiled during the compilation of the guests. I think the easiest design for this would require explicitly specifying which code can be compiled early like this. A higher-tech approach might be able to do static analysis for this. For the short term, a low-tech approach would be simply to put these parts in another module.
There’s also inclusive-range
if you want stop
to be inclusive. :)
I strongly recommend against include
. It will break lots of things — everything from separate compilation to IDE tools to macro hygiene.
The usual approaches for this are various forms of mutation. Here are three ways to do it: 1. Have one module export a mutator to allow setting the appropriate value. 2. Have one module export a parameter to allow setting the apporopirate value, and then reference everything via the parameter. 3. Use units.
great! exactly what I was looking for
Thanks all!
Hope Racket could support module mutual references in the future.
Unfortunately macros and mutually-recursive modules are fundamentally incompatible.
@samth what about having multi-file modules then? that’s basically what rust does, for example
That’s what I was about to say, but in fewer words. :)
yeah I think saying “pick two: separate compilation, cross-file cyclic references, and macros” is totally fine if you can opt-in to making compilation units larger than one file when you need to
@notjack I haven’t used Rust yet. Does it (multi-file modules) work like C# partial class? Sounds good to me.
@chansey97 In Rust, the compilation unit is an entire crate. Rust does not compile files separately, it only compiles crates separately, and if you change a single file you have to recompile the whole crate.
(“crate” in Rust means roughly the same thing as “package” in Racket)