-
Notifications
You must be signed in to change notification settings - Fork 0
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
Find functions passed as arguments #13
Comments
Ahh, yes, I understand now better. Correct, currently it will not identify any functions passed in through I'm not sure the way to have it capture this, but could look down a few potential paths...
Feel free to open a PR or give a suggestion if you have an idea on how to handle. |
I did some exploring and the functions get flagged as "symbol" when called this way. I tried a few approaches but nothing was robust and I wanted to jot down what I learned. Altogether this was my best attemptlibrary(tidyverse)
file_lines <- "
sapply(mtcars, min)
min(1:10) -> a
b = base::min(1:10)
d <- plot <- print <- NULL
agg <- function(x, fun) {
fn <-
switch(
fun,
avg = mean,
total = sum
)
fn(x, na.rm = TRUE)
}
agg(1:3, 'total')
"
exprs <- parse(text = file_lines)
parsed_df <- # standardize call, must be a better way to do this
exprs |>
as.character() |>
parse(text = _) |>
utils::getParseData(includeText = TRUE) |>
as_tibble() |>
print()
# github added all the 4-space tabs, I'm a 2-space guy 🚀
parsed_df |>
mutate(expr = cumsum(parent == 0)) |>
# not sure this fully drops formals
mutate(
is_formal = token %in% c("SYMBOL_FORMALS"),
is_new = lead(token, 2) %in% c("EQ_ASSIGN", "LEFT_ASSIGN")
) |>
filter(token %in% c("SYMBOL_FUNCTION_CALL", "SYMBOL")) |>
# not sure this is robust to drop new items used later
group_by(text) |>
filter(cumsum(is_new) == 0) |>
ungroup() |>
# shorten
distinct(text) |>
# figure out what things are
mutate(
from = map_chr(
text,
~find(.x) |> pluck(1) |> toString()
),
is = map_chr(
text,
~get0(.x) |> class() |> toString()
)
) |>
filter(is == "function" & from != ".GlobalEnv")
# text from is
# <chr> <chr> <chr>
# 1 sapply package:base function
# 2 min package:base function
# 3 switch package:base function
# 4 mean package:base function
# 5 sum package:base function All the caveats & info I foundWe could find all Lines 11 to 17 in e529b00
purrr::map_chr(
c("min", "mtcars"),
~get0(.x) |> class()
)
# [1] "function" "data.frame" Another problem with using the method above is formals being in the body of a function coming through as symbols. They start as exprs <- parse(text = file_lines)
map(exprs, globals::findGlobals)
# [[1]]
# [1] "sapply" "mtcars" "min"
#
# [[2]]
# [1] "<-" "{" "switch" "mean" "sum"
#
# [[3]]
# [1] "agg" ":" Another gotcha is an assignment like parse(text = "1:3 -> min") |> as.character()
# [1] "min <- 1:3" For this you could do something weird like below to standardize the call then use parse(text = "1 -> min; a = 23") |>
as.character() |>
parse(text = _) |>
getParseData(includeText = TRUE) |>
mutate(expr = cumsum(parent == 0)) I liked the There is a lot of info here re: Abstract Syntax Trees (AST). Some of these use cases are pretty niche maybe there is some low-hanging fruit in the mix? I'll try to take a closer look later. |
This is great, thanks for putting together. A few questions / comments: Do you need the whole file_lines <- "
sapply(mtcars, min)
min(1:10) -> a
b = base::min(1:10)
d <- plot <- print <- NULL
agg <- function(x, fun) {
fn <-
switch(
fun,
avg = mean,
total = sum
)
fn(x, na.rm = TRUE)
}
agg(1:3, 'total')
"
file_output <- tempfile(fileext = ".R")
writeLines(file_lines, file_output)
parsed_df <- utils::getParseData(parse(file_output, keep.source = TRUE) Currently in funspotr I just do the latter and don't see why you'd need the extra set-up in this case even for your later filtering steps...? I like your use of One thing this makes me think more about is how I want to handle functions that aren't installed locally. Currently if you have (Also should consider how much computational overhead |
Do you need the whole exprs |> as.character |> parse ... or would it just work using Even though it is right-assigned I like your use of get0(.x) |> class() though don't see why you'd need Ah, you're right, I don't need it for |
@rjake did not get to this before pushing to CRAN. Given the ambiguity here, one approach I was considering was making it so you could have an argument that allowed you to specify an approach. So say you want to do something like your approach above, you could pass this in as a function into a Actually setting this up though would require me to untangle a few steps in the current approach (that I may or may not get to). |
This is the use case I mentioned at the RStudio conference. I'd like to find functions called as arguments in apply functions or in something like
switch()
I am expecting to see
min
,mean
andsum
in the mixThe text was updated successfully, but these errors were encountered: