Open Goju-Ryu opened 1 month ago
I stumbled over this topic in numbat once earlier and I think it is actually possible at the moment to have cyclical dependencies of modules on each other as long as the actual functions/function calls in them don't cyclically depend on each other. Whichever module gets imported first will just be ignored the second time the same use
statement appears.
That said, cyclical imports may cause a lot of trouble and should be discouraged if not prohibited in my eyes.
I think it is actually possible at the moment to have cyclical dependencies of modules on each other as long as the actual functions/function calls in them don't cyclically depend on each other.
If so, I couldn't manage to make it work. I was developing a new function, so nothing else depended on it, but depending on module load order either lists or strings module threw an error due to an unrecognized name the first time the respective module used a function from the other.
I don't know your exact code of course but I would suspect that you have the use
statement before all functions needed by the other module are defined (probably on the very top of the file as we usually do). If you do it "over cross" it should work, e.g:
a.nbt:
# Functions to be used in b.nbt
fn f_a1(x) = x
fn f_a2(x) = x + 1
use b
# Functions using functions from b.nbt
fn f_a3(x) = f_b1(x) * f_b2(x)
b.nbt:
# Functions to be used in a.nbt
fn f_b1(x) = x
fn f_b2(x) = x - 1
use a
# Functions using functions from a.nbt
fn f_b3(x) = f_a1(x) + f_a2(x)
This is quite error prone and not very ergonomic of course which is why I think it should be avoided.
You are exactly right about how I did it. I see now why it didn't work the way I tried it. I very much agree that this should be avoided. I would rather make a new file than import mid file unless there is very compelling reason for it.
I agree with everything that has been said. If we implement more fine-grained importing and a better module structure, I think we could selectively import certain functions without causing trouble. This works well in languages like Rust, for example. Until then, let's split files and build a tree-like structure without any cycles, if possible.
I am considering an approach where I add a folder with the name of the file being split. Then the original file can just use the modules in the folder similar to prelude, but each file can be imported independently if desired.
So for example when splitting strings.nbt
the following file structure would result:
strings.nbt
strings/
foo.nbt
bar.nbt
Where the strings::foo
and strings::bar
modules are the modules the original strings module were split into.
Would that be an acceptable way to split the files?
edit: @Bzero has explained how it is currently possible to have cyclical dependencies in certain situations. Due to how error prone and messy it is likely to become, I think this issue should be more about agreeing on conventions for when we run into this. Which functions go where and what are the new files called and where are they placed?
I am writing some more utility functions for numbat, currently I'm working on str_split which splits a string into a list of strings based on a string pattern. I Would like to put this in the
core::strings
module as that fits it very well, but because I need thecore::lists
module and it already has a dependency on the former, this is not possible. I think it would be beneficial to either allow such cyclical dependencies or to document how such cases aught to be handled.I know F# disallows cyclical dependencies, so if we don't want to support it, maybe we could look to it for other approaches to handle such cases.