klmr / box

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

use()ing boxes and here::here() #259

Closed mbojan closed 2 years ago

mbojan commented 2 years ago

Please describe your feature request

I have a module in a root of a repo which is loaded by scripts in root but also in various subfolders of the repo. Is there a way of pointing box::use() to a submodule via a call to here::here() so that I can move the scripts around without worrying about updating the paths?

klmr commented 2 years ago

I’m not exactly sure what you mean by “move the scripts around”, and how here::here() would work with that. Do you have a concrete example (can be artificial)?

In general, here::here() isn’t required with modules, since both box::use and box::file work relative to the module they’re called in. That way, they will always find modules/files relative to the current module, without needing to resort to absolute file paths.

Besides this there’s also the option box.path (and its equivalent environment variable, R_BOX_PATH), which can be set to a vector of search paths for modules. But the intended use of that is for globally installed modules (somewhat equivalent to .libPaths()/R_LIBS_USER for packages), not for local submodules within a project.

mbojan commented 2 years ago

Apologies for being too terse in the initial description.

I'm using box in a project and have top level folder codebox containing a bunch of files (codebox/parsing.R, codebox/validity.R and so on) full of function definitions. So conceptually each file is a separate module, correct? Apart from those the project consists of various scripts using those functions via box::use(codebox/parsing) or box::use(../codebox/parsing) if the script is in some subfolder of the project. In that usecase box seems a convenient lightweight alternative to putting all the functions in an R package (no need to go through the whole build-install cycle each time something is updated but still better than plain source()ing).

Now, my point refers to loading the module from the scripts in that I need to make sure box::use() contains proper relative path to the codebox while, I imagine, could do something reducing to box::use(here::here("codebox", "parsing")) so that the path is always relative to the root of the repo.

klmr commented 2 years ago

I see! In this case, setting the box.path option should work:

options(box.path = c(here::here(), getOptions('box.path')))

This would need to be e.g. in a project-specific .Rprofile file. Then, box::use(codebox/whatever) should work everywhere in the project.

In general, though, the idea of ‘box’ is indeed for submodules within a project to use relative imports — that is, relative to the current module rather than relative to some encompassing project. In particular, relative imports (which start either with ./ or with ../) will always work, even in situations where here::here doesn’t.

But your use-case looks more like something where codebox is a global helper module, and in that case using the box.path option in combination with absolute imports (box::use(codebox/whatever)) seems the most convenient.

mbojan commented 2 years ago

Awesome, thanks!