Why can't this R call object in an expression be evaluated? (subsetting vs extracting from a call object)
Asked Answered
M

1

5

I am trying to understand the R expression object, but encountered some difficulties.

Code snippet:

a = 1
b = 2
x = expression(sin(a+b+1))
print(class(x[[1]][2]))
eval(x[[1]][2])

Results:

#////////////////////////////////////////////////////
x = expression(sin(a+b+1))
#////////////////////////////////////////////////////
print(class(x[[1]][2]))
[1] "call"
#////////////////////////////////////////////////////
x = expression(sin(a+b+1))
#////////////////////////////////////////////////////
print(class(x[[1]][2]))
[1] "call"
#////////////////////////////////////////////////////
eval(x[[1]][2])
Error in eval(expr, envir, enclos) : attempt to apply non-function
2: eval(expr, envir, enclos)
1: eval(x[[1]][2])

x[[1]][2] is a call object, but why can't it be evaluated?

Mayhem answered 23/4, 2014 at 15:15 Comment(0)
B
11

You should use the [[ operator, and not the [.

a <- 1
b <- 2
eval(x[[1]][[2]])
## [1] 4

This is because you'd like to extract information from the language object, and not to subset it (look inside the 2nd element, and not return a subsequence consisting of the 2nd element).

In other words, subsetting a call gives you a call:

x[[1]][2]
## (a + b + 1)()

and because there is no such function as a+b+1 (in fact, the result of a+b+1's evaluation is not a function object), R throws an error.

Interestingly, if + would return a function object, this could make sense:

"+" <- function(a, b) { function() print(":-)") }
(a+b+1)()
[1] ":-)"

On the other hand, extracting an element from a call object gives you an expression that can be evaluated:

x[[1]][[2]]
a + b + 1

(BTW, this expression is also a call, here equivalent to "+"(a, "+"(b, 1)).

EDIT. More formally, a call is an expression (sequence) of the form:

(f, a1, ..., an),

which we normally read as:

f(a1, ..., an).

Thus, the first element of the sequence is an object used to transform the other elements to get an output value.

Here x[[1]] is equivalent to:

(sin, a+b+1)

or, in more detail,

(sin, (+, a, (+, b, 1))).

Thus, x[[1]][2] takes a subsequence of the above consisting of only the 2nd element and returns:

((+, a, (+, b, 1)))

(i.e. (a+b+1)() - no args!). On the other hand x[[1]][[2]] extracts (looks inside) the 2nd element and gives:

(+, a, (+, b, 1)),

i.e. a+b+1 (note one pair of parentheses less).

EDIT2: All of this exemplifies the beauty and expressiveness of the R language, at least IMHO. Let's study another example in which we create a call f1(f2(f3, f4), f3, f4), which may be represented by a sequence

(f1, (f2, f3, f4), f3, f4).

We have:

f <- function(...) invisible(NULL)
f1 <- f; f2 <- f; f3 <- f; f4 <- f # for sake of clarity below
expr <- quote(f1(f2(f3, f4), f3, f4))
print(expr)
## f1(f2(f3, f4), f3, f4),           i.e. (f1, (f2, f3, f4), f3, f4)
print(expr[1:3])
## f1(f2(f3, f4), f3),               i.e. (f1, (f2, f3, f4), f3)
print(expr[3:4])
## f3(f4),                           i.e. (f3, f4)
print(expr[3])
## f3(),                             i.e. (f3)
expr[2]
## f2(f3, f4)(),                     i.e. ((f2, f3, f4)) [subsetting!]

And now for something completely different:

expr[[2]]
## f2(f3, f4),                       i.e. (f2, f3, f4) [extraction]

Hope this clarifies these issues a little bit.

Blowpipe answered 23/4, 2014 at 15:18 Comment(11)
Both class(x[[1]][[2]]) and class(x[[1]][2]) are calls.Mayhem
One gives you a call to (a+b+1)() and the other one to "+"(a, "+"(b, 1)).Blowpipe
I've made some edits, hope it clarifies more of this complicated topic. :)Blowpipe
And more edits (BTW, this book explains this topic in very detail, unfortunately it's in Polish...)Blowpipe
Oh, I would like to give you 10 upvotes! Is the book freely available?Mayhem
Unfortunately, the publisher does not allow its sharing. :(Blowpipe
I have a Polish colleague in the office!Mayhem
Is there a similar book in English?Mayhem
Try with Chamber's books, see #1745361Blowpipe
Another indicator of the difference: ≤n>h(x[[1]][2])length(x[[1]][2]) is 11 while ≤n>h(x[[1]][[2]])length(x[[1]][[2]]) is 33 . Annoyingly enough, tho' both eval(x[1]) and eval(x[[1]]) yield the desired calculation.Infract
This also makes sense for me: you can evaluate an expression (containing for example multiple calls == a sequence of calls) like x[1] or a single call like x[[1]]...Blowpipe

© 2022 - 2024 — McMap. All rights reserved.