Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle functions referenced by name as a string when using do.call() #302

Closed
bburns632 opened this issue Apr 14, 2023 · 6 comments · Fixed by #309
Closed

Handle functions referenced by name as a string when using do.call() #302

bburns632 opened this issue Apr 14, 2023 · 6 comments · Fixed by #309

Comments

@bburns632
Copy link
Collaborator

(I'm reposting a great question I received via email)

Does the pkgnet pick up function calls that use do.call()? It seems it doesn't. For example, within a function in a package I am working on, I have this call

nuis <- do.call(".estimate_nuisance",
args = c(data = list(data),
.key.inputs,
.nuis.inputs))

In this case pkgnet doesn't pick up the dependence on the function .estimate_nuisance(). In this particular package, this happens to be one of the key dependences, and we need to use do.call in order to tailor a bunch of options based on context, which we put in .nuis.inputs.

Is there an argument I can provide to CreatePackageReport() that would allow picking up dependencies through do.call()? If not, would you consider adding this?

@jameslamb
Copy link
Collaborator

I think this could be a useful feature, but I personally am not committing to working on it.

For anyone reading this post... I'd be happy to review a pull request proposing such a change. @bburns632 could you encourage whoever emailed you directly to come talk to us here, if they're interested in contributing that feature?

@bburns632
Copy link
Collaborator Author

bburns632 commented Apr 30, 2023 via email

@bburns632 bburns632 self-assigned this Apr 3, 2024
@bburns632
Copy link
Collaborator Author

bburns632 commented Apr 10, 2024

OK. Looking into this feature. Would like to bounce this idea off you @jayqi as I recall much of the current function network code originally written by you.

Why do this?
do.call is a fairly widely used function. Here's a quick search on github: https://github.com/search?q=do.call&type=code. Accommodating this will produce more correct function graphs for packages that rely on it (like the original requester's).

Why doesn't it work now?
do.call is a base function. Presently, our scope for nodes is limited to the target package namespace (e.g. names(pkgnet)). Base functions that are used within functions are overlooked.

Plan of attack
After brushing up on how we extract the function network (which ultimately a string search sorta thing), I think adding some code after this matching section to look for do.call specifically and do some special node additions to the edges & node list is the best place. Keep it similarly regex based with base functions.

https://github.com/uptake/pkgnet/blob/main/R/FunctionReporter.R#L361-L374

Design Decisions:

  1. How to reflect this in the graph?
    Do we include a do.call node that links to its argument or just link the function directly to the argument? For do.call, I could see either.

  2. Do we want to check for other base functions as well?
    We would need to limit expansion to those base functions directly referenced. We would still need special accommodation for do.call. This opens us up to a whole bunch of edgecases as well.

  3. Nested do.call: where there's a function in the list of arguments. Accommodate or not?
    Do not look recursively seems like a half measure, but that could get ugly. This is similar to Missing dependency link, if function call happens within the arguments of a function #304.

@jayqi
Copy link
Collaborator

jayqi commented Apr 10, 2024

With the caveat that I remember only like 5% of what we're doing here, how it works, why it works a certain way:

  • I don't think considering the target to be through do.call is the correct semantic representation here. The target function is directly referenced in the body of the source function, but it's just an argument of do.call rather than being called directly. Having the target be linked through do.call would be as if the body of do.call itself contained the target function.
    • As such, I don't think this is a case of "we exclude base functions but we should include them". I think this is a case of "do.call is a unique way of invoking a function that we currently miss".
    • Additionally IMO, do.call itself is not that interesting and is just machinery for calling the target function being used. I don't think adding a node for do.call would add any value to the graph.
  • Is this actually a case of whether or not the function is specified as a string reference to a name? i.e., is there a difference between do.call(my_func, args = ...) vs. do.call("my_func", args = ...)? I'm wondering if the former currently works fine, and it's just the latter that doesn't work. If so, this is more like a bug/missed edge case, and not really a change in functionality. Then we basically just need to special case the latter, because IIRC we currently discard atomic strings, but do.call is special because it will look up the string reference.

@bburns632
Copy link
Collaborator Author

@jayqi, on your second point, here are some quick edits on baseballstats. Looks like we currently support the case with a function argument to do.call. It's the function text name case that is not supported.

Normal (main branch):
Screenshot 2024-04-09 at 10 49 47 PM
at_bat directly called

With text as a param:
Screenshot 2024-04-09 at 10 48 14 PM
docall calling at_bat

With function as a param:
image
image

Between each, I am quitting R without memory and recreating with this code chunk:

library(devtools)
remove.packages("pkgnet")
devtools::install_local(".", force = TRUE, upgrade = "never")
devtools::install_local("inst/baseballstats", force = TRUE,upgrade = "never")
library(pkgnet)
t <- CreatePackageReport("baseballstats")

@bburns632
Copy link
Collaborator Author

Working on the edge case handling now

@jayqi jayqi linked a pull request Apr 11, 2024 that will close this issue
@jayqi jayqi changed the title Recognition of Functions that use do.call()? Handle functions referenced by name as a string when using do.call() Apr 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants