from an expression to a language object - r

I would like to use microbenchmark::microbenchmark(), but with arguments in the ... that I programatically construct, with strings.
Example :
# classic way
microbenchmark(head(1:1e6), head(1:1e8), times = 10) # (interesting...)
# define my arguments
functionStrings <- lapply(c(6,8), function(ni) paste0("head(1:1e", ni, ")"))
# will not work
do.call(microbenchmark, lapply(functionStrings, function(vi) parse(text=vi)))
The problem here is that microbenchmark works with unevaluated expressions, of class "language", that it then deparse(). Doing deparse() on a normal expression unfortunately deparse the expression object...
bla <- quote(head(1:1e6))
blou <- parse(text = "head(1:1e6)")
eval(bla) ; eval(blou) # Same thing, but....
deparse(bla)
deparse(parse(text = "head(1:1e6)"))
How to get from a string or an expression (argument or output of parse() above) to a language object (output of quote() above) ?

The anonymous function in the last line of the first block of code should be:
function(vi) parse(text=vi)[[1]]
That is:
the argument to parse is named text and not test and
a call object is needed rather than an expression so use [[1]] .

Related

Why is parse(text=) throwing an error when there is a comma in the text?

Inside of a Shiny app I am combining levels to make a ID that will be used for ggplot faceting.
Here is an example of what works when doing it by hand:
paste0(data[[1]],', ',data[[2]])
However, the user can select whatever and how ever many identifying columns they want, so I am automating it like this:
First I build combo_ID_build from whatever columns they have selected in UI1 (which is numeric with column numbers for whatever the user selected).
for (i in UI1){
combo_ID_build<-paste0(combo_ID_build,"data[[",i,"]]")
if(i != tail(UI1,1)){
combo_ID_build<-paste0(combo_ID_build,",', ',")
}
}
Then I use eval(parse())
ID <- paste0(eval(parse(text = combo_ID_build)))
So for this example, the user selects columns 1 and 2, so combo_ID_build would be "data[[1]],', ',data[[2]]" where str(combo_ID_build) is chr.
However, when parsed this throws an error, "Error in parse(text = combo_ID_build) : :1:10: unexpected ','
1: data[[1]],"
I tried escaping the comma with \ and \\ but it only throws an error about the backslashes being unrecognized escape strings.
What am I doing wrong here?
You eval(parse()) tries to parse and evaluate an expression. You run
eval(parse(text = "data[[1]],', ',data[[2]]"))
That's equivalent to running the command
data[[1]], ', ', data[[2]]
which indeed is a syntax error. It doesn't matter that you wrapped it in paste0, when you have foo(bar()) foo() will run on the output of bar(). If bar() can't run on its own, there will be an error. You have paste0(eval(parse()), paste0() will run on the output of eval(). So what you eval() needs to run on its own before paste0() is called.
eval(parse()) should usually be avoided. In this case, I think you're looking for do.call, which lets you call a function on a set of arguments in a list. Let's also use paste with sep = ", " to simplify things:
# no loop or eval/parse needed
combo_ID_build = do.call(what = \(x) paste(x, sep = ", "), args = data[UI1])
In this particular case, we can simplify even more by using the utility function interaction() which is made for this use case:
combo_ID_build = interaction(data[UI1], sep = ", ")
I'll leave you with this:
fortunes::fortune(106)
# If the answer is parse() you should usually rethink the question.
# -- Thomas Lumley
# R-help (February 2005)

transfer character in function to the function in r

I have such a problem, I want to define the grad function as an input of the function. Please see the example below.
f<-function(grad="2*m-4"){
y=grad
return(y)
}
f(3)=2
I'm usually not a fan of using eval(parse(text=..)), but it does do this with a character string:
f <- function(m, grad="2*m-4"){
eval(parse(text = grad))
}
f(3)
# [1] 2
The two take-aways:
if your grad formula requires variables, you should make them arguments of your function; and
parse(text=..) parse the string as if the user typed it in to R's interpreter, and it returns an expression:
parse(text="2*m - 4")
# expression(2*m - 4)
This expression can then be evaluated with eval.

Is there an equivalent of the ast.literal_eval python method in R

Unlike the eval function, ast.literal_eval function safely evaluates an expression node or a string containing a Python literal or container display. The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None. I.e. It only evaluates the strings containing literal or containers, it does not evaluate the strings containing code!
I am wondering if there is an equivalent of the literal_eval method in R? Thanks in advance!
Reference:
ast.literal_eval function
The answer to safely evaluating arithmetic expressions allows to create a function resembling ast.literal_eval for string input.
This function takes as arguments the input string to evaluate, as well as the allowed operations, with default values similar to what ast.literal_eval allows :
literal_eval <- function(input, allowed = c("list", "c", "+", "-", "/", "*")) {
# Create safe empty environment
safe_env <- new.env(parent = emptyenv())
# assign allowed functions
lapply(allowed,function(f) assign(f,get(f, "package:base"),safe_env))
# Evaluate input
safe_env$expr <- parse(text = input)
eval(substitute(expr,env = safe_env), env = safe_env)
}
literal_eval("1+1")
[1] 2
literal_eval("c(1,2)")
[1] 1 2
literal_eval("list(2,3)")
[[1]]
[1] 2
[[2]]
[1] 3
literal_eval("system('delete *.*')")
Error in system('delete *.*') : unknown function "system"

do.call() doesn't like base function "c" with a list

I have a larger section of code but I've narrowed down the problem to this -
So I want to return a concatenated list.
do.call(c,"X")
Error in do.call(c, "X") : second argument must be a list
So above it complains about the SECOND argument not being a list.
asimplelist=list(2,3,4)
class(asimplelist)
[1] "list"
do.call(c,asimplelist)
Error in do.call(c, asimplelist) :
'what' must be a function or character string
Why will this not return a concatenated list ? C is a legit function, and it's being passed a list?
args(do.call)
function (what, args, quote = FALSE, envir = parent.frame())
NULL
So "what" is the function argument it is complaining about.
I will answer "stealing" my answer from this comment by Nick Kennedy:
It might be better to put the c in double quotes.
If the user has a non-function named c in the global environment, do.call(c, dates) will fail with the error "Error in do.call(c, list(1:3)) : 'what' must be a character string or a function".
Clearly it may not be best practice to define c, but it's quite common for people to do a <- 1; b <- 2; c <- 3.
For most purposes, R still works fine in this scenario; c(1, 2) will still work, but do.call(c, x) won't.
Of course if the user has redefined c to be a function (e.g. c <- sum), then do.call will use the redefined function.

Finding the names of all functions in an R expression

I'm trying to find the names of all the functions used in an arbitrary legal R expression, but I can't find a function that will flag the below example as a function instead of a name.
test <- expression(
this_is_a_function <- function(var1, var2){
this_is_a_function(var1-1, var2)
})
all.vars(test, functions = FALSE)
[1] "this_is_a_function" "var1" "var2"
all.vars(expr, functions = FALSE) seems to return functions declarations (f <- function(){}) in the expression, while filtering out function calls ('+'(1,2), ...).
Is there any function - in the core libraries or elsewhere - that will flag 'this_is_a_function' as a function, not a name? It needs to work on arbitrary expressions, that are syntactically legal but might not evaluate correctly (e.g '+'(1, 'duck'))
I've found similar questions, but they don't seem to contain the solution.
If clarification is needed, leave a comment below. I'm using the parser package to parse the expressions.
Edit: #Hadley
I have expressions with contain entire scripts, which usually consist of a main function containing nested function definitions, with a call to the main function at the end of the script.
Functions are all defined inside the expressions, and I don't mind if I have to include '<-' and '{', since I can easy filter them out myself.
The motivation is to take all my R scripts and gather basic statistics about how my use of functions has changed over time.
Edit: Current Solution
A Regex-based approach grabs the function definitions, combined with the method in James' comment to grab function calls. Usually works, since I never use right-hand assignment.
function_usage <- function(code_string){
# takes a script, extracts function definitions
require(stringr)
code_string <- str_replace(code_string, 'expression\\(', '')
equal_assign <- '.+[ \n]+<-[ \n]+function'
arrow_assign <- '.+[ \n]+=[ \n]+function'
function_names <- sapply(
strsplit(
str_match(code_string, equal_assign), split = '[ \n]+<-'),
function(x) x[1])
function_names <- c(function_names, sapply(
strsplit(
str_match(code_string, arrow_assign), split = '[ \n]+='),
function(x) x[1]))
return(table(function_names))
}
Short answer: is.function checks whether a variable actually holds a function. This does not work on (unevaluated) calls because they are calls. You also need to take care of masking:
mean <- mean (x)
Longer answer:
IMHO there is a big difference between the two occurences of this_is_a_function.
In the first case you'll assign a function to the variable with name this_is_a_function once you evaluate the expression. The difference is the same difference as between 2+2 and 4.
However, just finding <- function () does not guarantee that the result is a function:
f <- function (x) {x + 1} (2)
The second occurrence is syntactically a function call. You can determine from the expression that a variable called this_is_a_function which holds a function needs to exist in order for the call to evaluate properly. BUT: you don't know whether it exists from that statement alone. however, you can check whether such a variable exists, and whether it is a function.
The fact that functions are stored in variables like other types of data, too, means that in the first case you can know that the result of function () will be function and from that conclude that immediately after this expression is evaluated, the variable with name this_is_a_function will hold a function.
However, R is full of names and functions: "->" is the name of the assignment function (a variable holding the assignment function) ...
After evaluating the expression, you can verify this by is.function (this_is_a_function).
However, this is by no means the only expression that returns a function: Think of
f <- function () {g <- function (){}}
> body (f)[[2]][[3]]
function() {
}
> class (body (f)[[2]][[3]])
[1] "call"
> class (eval (body (f)[[2]][[3]]))
[1] "function"
all.vars(expr, functions = FALSE) seems to return functions declarations (f <- function(){}) in the expression, while filtering out function calls ('+'(1,2), ...).
I'd say it is the other way round: in that expression f is the variable (name) which will be asssigned the function (once the call is evaluated). + (1, 2) evaluates to a numeric. Unless you keep it from doing so.
e <- expression (1 + 2)
> e <- expression (1 + 2)
> e [[1]]
1 + 2
> e [[1]][[1]]
`+`
> class (e [[1]][[1]])
[1] "name"
> eval (e [[1]][[1]])
function (e1, e2) .Primitive("+")
> class (eval (e [[1]][[1]]))
[1] "function"
Instead of looking for function definitions, which is going to be effectively impossible to do correctly without actually evaluating the functions, it will be easier to look for function calls.
The following function recursively spiders the expression/call tree returning the names of all objects that are called like a function:
find_calls <- function(x) {
# Base case
if (!is.recursive(x)) return()
recurse <- function(x) {
sort(unique(as.character(unlist(lapply(x, find_calls)))))
}
if (is.call(x)) {
f_name <- as.character(x[[1]])
c(f_name, recurse(x[-1]))
} else {
recurse(x)
}
}
It works as expected for a simple test case:
x <- expression({
f(3, g())
h <- function(x, y) {
i()
j()
k(l())
}
})
find_calls(x)
# [1] "{" "<-" "f" "function" "g" "i" "j"
# [8] "k" "l"
Just to follow up here as I have also been dealing with this problem: I have now created a C-level function to do this using code very similar to the C implementation of all.names and all.vars in base R. It however only works with objects of type "language" i.e. function calls, not type "expression". Demonstration:
ex = quote(sum(x) + mean(y) / z)
all.names(ex)
#> [1] "+" "sum" "x" "/" "mean" "y" "z"
all.vars(ex)
#> [1] "x" "y" "z"
collapse::all_funs(ex)
#> [1] "+" "sum" "/" "mean"
Created on 2022-08-17 by the reprex package (v2.0.1)
This generalizes to arbitrarily complex nested calls.

Resources