Avoiding the Infamous "Eval(Parse())" Construct

Avoiding the infamous eval(parse()) construct

Using get and [[:

bar <- list(foo = list(fast = 1:5, slow = 6:10),
oof = list(6:10, 1:5))

rab <- 'bar'

get(rab)[['oof']]
# [[1]]
# [1] 6 7 8 9 10
#
# [[2]]
# [1] 1 2 3 4 5

How do I avoid eval and parse?

For what its worth, the function source actually uses eval(parse(...)), albeit in a somewhat subtle way. First, .Internal(parse(...)) is used to create expressions, which after more processing are later passed to eval. So eval(parse(...)) seems to be good enough for the R core team in this instance.

That said, you don't need to jump through hoops to source functions into a new environment. source provides an argument local that can be used for precisely this.

local: TRUE, FALSE or an environment, determining where the parsed expressions are evaluated.

An example:

env = new.env()
source('test.r', local = env)

testing it works:

env$test('hello', 'world')
# [1] "hello world"
ls(pattern = 'test')
# character(0)

And an example test.r file to use this on:

test = function(a,b) paste(a,b)

What specifically are the dangers of eval(parse(...))?

Most of the arguments against eval(parse(...)) arise not because of security concerns, after all, no claims are made about R being a safe interface to expose to the Internet, but rather because such code is generally doing things that can be accomplished using less obscure methods, i.e. methods that are both quicker and more human parse-able. The R language is supposed to be high-level, so the preference of the cognoscenti (and I do not consider myself in that group) is to see code that is both compact and expressive.

So the danger is that eval(parse(..)) is a backdoor method of getting around lack of knowledge and the hope in raising that barrier is that people will improve their use of the R language. The door remains open but the hope is for more expressive use of other features. Carl Witthoft's question earlier today illustrated not knowing that the get function was available, and the question he linked to exposed a lack of understanding of how the [[ function behaved (and how $ was more limited than [[). In both cases an eval(parse(..)) solution could be constructed, but it was clunkier and less clear than the alternative.

Avoiding eval(parse(text)) When Operating On Similarly Named Columns

I find a for loop easier to write and read :

basevars = c("a","b","c","d")
for (i in basevars)
DT[, paste0(i,"_compare"):=get(paste0(i,"_pre"))/get(paste0(i,"_post"))]

I've never really known why R couldn't just define + to work on strings. It's an error currently, so it's not like it's used or anything :

> "a"+"b"
Error in "a" + "b" : non-numeric argument to binary operator

Otherwise you could simply do :

for (i in basevars)
DT[, i+"_compare" := get(i+"_pre")/get(i+"_post")]

Avoiding eval(parse()) in building fractional polynomial function

First we create a function that will generate a single term in the sequence

one <- function(p, c = 1, repeated = 1) {
if (p == 0) {
lhs <- substitute(c * log(x), list(c = c))
} else {
lhs <- substitute(c * x ^ p, list(c = c, p = p))
}

if (repeated == 1) return(lhs)
substitute(lhs * log(x) ^ pow, list(lhs = lhs, pow = repeated - 1))
}
one(0)
# 1 * log(x)
one(2)
# 1 * x^2

one(2, 2)
# 2 * x^2

one(2, r = 2)
# 1 * x ^ 2 * log(x)^1
one(2, r = 3)
# 1 * x ^ 2 * log(x)^2

The key tool here is substitute() which is explained here.

Next we write a function that will add together two terms. Again this uses substitute:

add_expr_1 <- function(x, y) {
substitute(x + y, list(x = x, y = y))
}

add_expr_1(one(0, 1), one(2, 1))

We can use this to make a function to add together any number of terms:

add_expr <- function(x) Reduce(add_expr_1, x)
add_expr(list(one(0, 1), one(1, 1), one(2, 3)))

With these piece in place, the final function is simple - we figure out the number of reps, then use Map() to call one() once for each combination of powers, coefs and reps:

fp <- function(powers, coefs) {
# Determine number of times each power is repeated. This is too
# clever approach but I think it works
reps <- ave(powers, powers, FUN = seq_along)

# Now generate a list of expressions using one
components <- Map(one, powers, coefs, reps)

# And combine them together with plus
add_expr(components)
}

powers <- c(-1, 0, 0.5, 0.5, 3)
coefs <- c(-1.5, -14, -13, 6, 1)
fp(powers, coefs)
# -1.5 * x^-1 + -14 * log(x) + -13 * x^0.5 + 6 * x^0.5 * log(x)^1 +
# 1 * x^3

Avoiding eval-parse or do.call

We may use getFunction

library(ggplot2)
p1 <- p +
getFunction(all_ggplot2_funs[grep("theme_", all_ggplot2_funs)][15])()

-checking

> p2 <- p + theme_minimal()
> all.equal(p1, p2)
[1] TRUE

R: eval(parse(...)) is often suboptimal

Actually the list probably looks a bit different. The '$' convention is somewhat misleading. Try this:

dat[["orders"]][[ or_ID ]][["price"]]

The '$' does not evaluate its arguments, but "[[" does, so or_ID will get turned into "5810584".



Related Topics



Leave a reply



Submit