klmr / box

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

Get the module a function is defined in #310

Closed TransGirlCodes closed 1 year ago

TransGirlCodes commented 1 year ago

I'm getting my workplace to orgaize its code into modules and using box, and I was going to use the lifecycle package to help with deprecations and such, but I'm finding using lifecycle functions inside functions defined in modules to cause errors during deprecation warnings - it tries to do some parent.call sort of stuff to detect the package of the function that calls the lifecycle deprecation function. Rather than try and persuade the authors to support modules, I thought perhaps a simple box based module that implements similar functionality would be a cool project.

The big question I have as to whether this is possible is, is there a way to obtain the name of the module that a function is defined in?

TransGirlCodes commented 1 year ago

So as an update, I did some experimenting with this. I cracked open the box::name function, and found the box:::current_mod method, which I tested as follows.

File deprecation_module.R This will house the functions that do the deprecation warnings. They want to know the module from which they were called.

#' @export
deprecate_func <- function() {
  box:::current_mod()
}

Two test modules. Housing some dummy functions that are "deprecated" - they just call the one in the deprecation module. For the test they have identical code:


box::use(
  dm = ./deprecation_module
)

beep <- function() {
  dm$deprecate_func()
}

Finally the driver file I run lines from in the REPL:

box::use(./test_module, ./test_module_boogaloo)

test_module$beep()
test_module_boogaloo$beep()

I get the following output:

> box::use(./test_module, ./test_module_boogaloo)
> test_module$beep()
<environment: 0x00000203c7b3efd8>
attr(,"name")
[1] "namespace:test_module"
attr(,"class")
[1] "box$ns"
attr(,"loading")
[1] FALSE

> test_module_boogaloo$beep()
<environment: 0x00000203cc318fd8>
attr(,"name")
[1] "namespace:test_module_boogaloo"
attr(,"class")
[1] "box$ns"
attr(,"loading")
[1] FALSE

So it looks like this does exactly as I'd like. I does feel weird using ::: in a box module as everything is ideally supposed to be imported using box::use to follow the explicit is better than implicit idiom.

TransGirlCodes commented 1 year ago

Looking at the source, on line https://github.com/klmr/box/blob/d3e0f1db56e0ef241cf89ec9064be7b598912e09/R/env.r#L113 There is a question as to whether the function should be exported, could it be? It would help my usecase if there were an exported function to do this, otherwise I can use ::: or mirror the functions in my own codebase, but then that risks messing up if the internals of how box works ever changed.

klmr commented 1 year ago

is there a way to obtain the name of the module that a function is defined in?

You’ve already found box::name(), which gives you this information for the current module. The way I understand your issue you want to find out the name of the calling module, right? In that case, you could probably do something as follows in your deprecation_module.R:

#' @export
deprecate_func <- function() {
  calling_module_name <- evalq(box::name(), envir = parent.frame())
  # …
}

I don’t think you should need to use the unexported box:::current_mod() function here. In fact I will probably remove this function in the near future: it’s an internal convenience function that does not actually add any convenience. Instead, I might (probably will) export box::mod_topenv() in the future, most likely after renaming it to box::topenv().

Please let me know if I’ve misunderstood your use-case: I believe the above should be sufficient for you, and you don’t actually need any new functionality.


List of tasks to implement box::topenv (note to self):

TransGirlCodes commented 1 year ago

That makes sense, thanks!