sharkdp / numbat

A statically typed programming language for scientific computations with first class support for physical dimensions and units
https://numbat.dev
Apache License 2.0
1.26k stars 53 forks source link

Cyclical imports of modules #627

Open Goju-Ryu opened 1 month ago

Goju-Ryu commented 1 month ago

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 the core::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.

Bzero commented 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.

Goju-Ryu commented 1 month ago

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.

Bzero commented 4 weeks ago

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.

Goju-Ryu commented 4 weeks ago

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.

sharkdp commented 3 weeks ago

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.

Goju-Ryu commented 3 weeks ago

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?