klmr / box

Write reusable, composable and modular R code
https://klmr.me/box/
MIT License
829 stars 47 forks source link

Nested packages and relative paths #322

Closed jordantgh closed 8 months ago

jordantgh commented 1 year ago

Please describe your feature request

Suppose I have a project with a root dir, a data dir, and a code dir. I have a script that calls module foo, stored in the I can write the following:

script.r:

box::use(./code/foo)

When executed from the root dir, this code will work.

Suppose foo calls a module bar, and I write it like so:

foo.r:

box::use(./code/bar)

In this case, if I run script.r, it will fail, because box::use() will treat the . as /rootdir/code/. I realise this is likely a design choice, but it has the downside that the script cannot be executed standalone from the project root. In my case, I am trying to orchestrate data analysis pipelines for which running individual component scripts on their own is a frequent enough task that expecting users to naviagate around the terminal is a nuisance.

I thought the solution would be to use box.path, set in .Rprofile with here(), but this does not appear to work. It only works for the first "layer" of box::use() calls, but it appears that in nested calls, the parent environment is not inherited, and it does not run the .Rprofile in rootdir because that is no longer the working directory. Would there be a way to implement this, or is there a workaround (besides setting box.path in every module)?

Thanks for the awesome package.

klmr commented 1 year ago

the script cannot be executed standalone from the project root

Could you explain how exactly you are executing the script in this context (e.g.: are you running Rscript code/myscript.r from the command line?)? It should usually just work (with an import name relative to the calling module; that is: with box::use(./bar) inside the module code/foo.r).

Your use-case should definitely work, but I need more details to understand whether it is already working or whether I overlooked something that would require fixing internals in ‘box’ to make this work.

I thought the solution would be to use box.path, set in .Rprofile with here()

Setting box.path inside a project should usually not be necessary, although I don’t want to exclude the possibility that there are scenarios where it makes sense. Conversely, using here::here() will probably not do anything useful, because here::here is broken by design1, and ‘box’ does a lot of work internally to actually implement that same functionality correctly.


1 In a nutshell, here::here is designed for and only works inside R projects following a narrowly defined structure that is not suitable for anything except notebook-style data analysis projects.

(Incidentally, you are right that the actual behaviour is a design choice; in fact, virtually every other module system behaves in the same way, because doing it differently would break encapsulation.)

klmr commented 8 months ago

As explained, I believe that this should work using box::use(./bar) inside code/foo.r.

Please reopen with explanation if the issue isn’t solved.