wahani / modules

Modules in R

Home Page:https://cran.r-project.org/package=modules

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Circular 'use' (infinite loop)

mmuurr opened this issue · comments

It appears use (as a module-like replacement for small R packages) cannot reference each other in a circular fashion. Obviously top-level objects referring to each other can cause a problem (when one doesn't exist yet), but in the scenario below there's (I think) a valid use for computing the 'closure' of a module loading chain.

Here's module "a" (a.R):

b <- modules::use("b.R")
val_a <- "a!"
fun_a <- function() {
  print("in fun_a, printing b$val_b")
  print(b$val_b)
}

Here's module "b" (b.R):

a <- modules::use("a.R")
val_b <- "b!"
fun_b <- function() {
  print("in fun_b, calling a$fun_a")
  a$fun_a()
}

Now if you try to use module "b" in your main program like so:

b <- modules::use("b.R")
# b$fun_b()  ## never gets here because the above line infinitely recurses

... you'll get the infinite recursion "C stack usage ... is to close to the limit" error.

In the case above, while the modules are being sourced, neither needs a direct reference to anything in the other. Only upon execution of b$fun_b will it need a reference to a$fun_a, which then needs a reference to b$val_b. All of these values should be available at runtime, and indeed this will work fine with packages.

Curious to hear your thoughts about how this might be implemented in modules?

I would not recommend using circular dependencies at all which is also why I would not try to make them possible. From wikipedia (https://en.wikipedia.org/wiki/Circular_dependency):

in software design, circular dependencies between larger software modules are considered an anti-pattern because of their negative effects.

If you have package A and B and they both depend on each other, you cannot install any of them on a fresh system. At least as long as you have maintained the depends/import field in the DESCRIPTION. So there is no such relationship between packages present on CRAN. I haven't checked though.

What you should do instead is follow the ADP: https://en.wikipedia.org/wiki/Acyclic_dependencies_principle A simple way out is:

  • refactor the parts module A and B depend on into a mode C
  • module A and B can then safely depend on C

Also from the docs of the package itself https://cran.r-project.org/web/packages/modules/vignettes/modulesAsFiles.html:

Modules in files should not load other modules in other files. You should view a module as a stand alone and self-contained unit. Dependencies should refer to packages if possible. The benefit is ease of reuse. If your modules do depend on each other, you use dependency injection to encode these relationships. See the vignette on modules as objects.

Hope that clarifies some of it. Feel free to answer any questions around this topic or give additional context to what you want to do.

I will close this due to inactivity. Feel free to reopen and discuss.