8

I noticed the following when using '[<-'. I am successful at replacing elements but not at appending an element to the vector.

Example:

VarX <- integer()
VarX[1] <- 11
`[<-`(VarX, 2, 22)
VarX
# [1] 11

# Expected the value of VarX to be:  
# [1] 11 22

# Also tried: 
`[<-`(VarX, i=2, value=22)
VarX 
# [1] 11

However, if there is already a value at the index, the value does get replaced.

VarX <- integer()
VarX[1] <- 11
VarX[2] <- 99
VarX
# [1] 11 99
`[<-`(VarX, 2, 22)
VarX
# [1] 11 22

Do I simply have the syntax wrong, or is this as intended? Any further insight into what is going on here would be appreciated.

Please note, there is no concrete objective here other than to better understand the language.

Update regarding @Roland and @Dason 's comments.

It appears that the behavior is tied to how the values of the object are initially assigned. For example, when the value assigned to VarX is 1:2 versus c(1, 2) the behavior of [<-(VarX, 2, 22) gives different results, as shown below:

### changes not saved to VarX

rm(VarX)  # actually ran:     rm(list=ls(all=TRUE))
VarX <- 1:2
VarX
# [1] 1 2

`[<-`(VarX, 2, 22)
# [1]  1 22

VarX
# [1] 1 2

### changes ARE saved to VarX

rm(VarX)  # actually ran:     rm(list=ls(all=TRUE))
VarX <- c(1, 2)
VarX[2] <- 2
VarX
# [1] 1 2

`[<-`(VarX, 2, 22)
# [1]  1 22

VarX
# [1]  1 22



> sessionInfo()
R version 2.15.1 (2012-06-22)
Platform: x86_64-apple-darwin9.8.0/x86_64 (64-bit)

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base  
Ricardo Saporta
  • 54,400
  • 17
  • 144
  • 178
  • 1
    This works just fine: `VarX <- integer(); VarX[1] <- 11; VarX[2] <- 22`. Why are you trying to call the function in a non-standard way? – Joshua Ulrich Mar 02 '13 at 19:38
  • I get different behaviour (and basically the same for replacement and appending). ` `[<-` ` `(VarX, 2, 22)` prints `[1] 11 22`, but `VarX` gives `[1] 11` and `[1] 11 99`, respectively, afterwards. – Roland Mar 02 '13 at 19:49
  • 1
    Can you rerun your code sections in a clean session? I can't find a case where `[<-` ever actually modifies the first parameter. It returns what the first parameter would be if it was modified but doesn't do the actual assignment. – Dason Mar 02 '13 at 19:57
  • @Roland, it appears this might be related to how `VarX` was originally assigned. Please see the update and let me know if you get the same results or not – Ricardo Saporta Mar 02 '13 at 20:16
  • Nope. Cannot reproduce your output with R 2.15.2. – Roland Mar 02 '13 at 20:21
  • 2
    I just started a clean session and now get the same as you. It seems like some package modified the primitive. – Roland Mar 02 '13 at 20:25
  • After some testing: I use Rstudio. If I send the whole code block to R, it actually does assign. If I send the code line by line, the behavior is as documented (see Matthew's answer). Seems to be some Rstudio magic. – Roland Mar 02 '13 at 20:34
  • (Allways refering to the last example.) Just tried it running R from the MacOSX terminal and there it does assign. – Roland Mar 02 '13 at 20:46
  • 2
    @JoshuaUlrich why not? I think if you try to 'break' the language in some way and understand the how and why behind what you did then you are a better programmer going forward! I found this interesting. – Simon O'Hanlon Mar 02 '13 at 21:00

1 Answers1

11

The function '[<-' might not replace anything in its first argument. In certain circumstances, it makes a copy of the object and modifies that.

See section 3.4.4 of the language definition:

x[3:5] <- 13:15

The result of this commands is as if the following had been executed

‘*tmp*‘ <- x
x <- "[<-"(‘*tmp*‘, 3:5, value=13:15)
rm(‘*tmp*‘)

This is essentially what will be run if the structure of x must be modified. However, it is clear based on experiments of the OP (and others, including myself) that the "[<-" function can modify elements in-place. Clearly nothing can be done in-place if the entire object is going to be replaced.

In-place substitution:

> x <- 1:2
> class(x)
[1] "integer"
> `[<-`(x, 2, 99L)
[1]  1 99
> x
[1]  1 99

Replacement of the entire object because the type has been changed (in the C function SubAssignTypeFix):

> x <- 1:2
> class(x)
[1] "integer"
> x[2] <- 99
> class(x)
[1] "numeric"

Another situation where the object is replaced, is when there is more than one reference to the object being modified:

x <- 1:2
y <- x
`[<-`(x, 2, 99L)
## [1]  1 99
x
## [1] 1 2

Running R under the debugger shows that the assignment function called indirectly via x[2] <- 99 invokes the C function do_set, whereas this function is not called when the assignment function is called directly by name.

do_set calls a function defineVar which modifies the appropriate environment. In the case of an in-place replacement, the object replaces itself in the environment, which are the exact cases where calling the assignment function by name results in the object being modified (a copy was not taken).

Interesting tidbit (and see here: R object identity)

#### R console:
x <- 1:2
.Internal(inspect(x))
## @26b27a8 13 INTSXP g0c1 [NAM(1)] (len=2, tl=0) 1,2
x[2] <- 99

#### gdb:
Breakpoint 7, do_set (call=0x2773640, op=0x169e668, args=0x2773870, rho=0x16c6b68) at eval.c:1732   
(gdb) p s
## $135 = (SEXP) 0x192bee0
c

#### R console:
.Internal(inspect(x))
## @192bee0 14 REALSXP g0c2 [NAM(1)] (len=2, tl=0) 1,99

To directly answer the original question, when [<- enlarges the vector, a copy is made. From the function EnlargeVector at subassign.c:113:

PROTECT(newx = allocVector(TYPEOF(x), newlen));

/* Copy the elements into place. */
...

This is R 2.15.2, which I built from source without optimization and with debugging info. It is very slow without optimization.

Community
  • 1
  • 1
Matthew Lundberg
  • 42,009
  • 6
  • 90
  • 112
  • Thanks @Matthew, but then why the discrepancy based on what is assigned to the original var? – Ricardo Saporta Mar 02 '13 at 20:28
  • @RicardoSaporta -- b/c when it's used as a replacement operator, `[<-` *doesn't* actually make a new copy of the whole object, whereas it does when appending. It passes/modifies by reference in the first place, but by value in the second. (Not sure if that's the right terminology, so please, anybody, let me know if it's not.) – Josh O'Brien Mar 02 '13 at 20:36
  • @JoshO'Brien I think he's referring to the discrepancy in the last two code blocks in the updated question. – Dason Mar 02 '13 at 20:37
  • @JoshO'Brien But in those last two examples there isn't any appending going on. It's just a difference in how VarX was constructed. – Dason Mar 02 '13 at 20:41
  • 1
    @Dason -- Oh. I see. That's being caused because when you try to assign 22 (a `numeric`) to an element of an `integer` vector, you *do* force R to make a copy of the vector, to convert its mode to `numeric`. In the first block, this *will* work, `"[<-"(VarX, 2, 22L)`, because `22L` is of the same mode as the vector into which it's being assigned. **Short answer:** Compare `typeof(1:2); typeof(c(1,2)); typeof(22)`. – Josh O'Brien Mar 02 '13 at 20:45
  • 1
    @JoshO'Brien I think you should post that as an answer – Dason Mar 02 '13 at 21:05
  • @JoshO'Brien, very interesting!! Can you add that as an answer? – Ricardo Saporta Mar 02 '13 at 21:07
  • @Dason & Ricardo -- I don't have the time right now, so either of you should feel free to add it as answer. It *is* interesting. Alternatively, you or Matthew could at it to his answer. – Josh O'Brien Mar 02 '13 at 21:17
  • I suspect case 2 is technically a bug in R. – hadley Mar 04 '13 at 13:30
  • @hadley I don't think it's a bug. R avoids making a copy when possible, by modifying the original object. – Matthew Lundberg Mar 11 '13 at 04:02
  • @MatthewLundberg but it should only do that when `[<-` is called as a replacement function, not as a regular function. – hadley Mar 12 '13 at 00:29
  • @hadley I hadn't considered that. Interesting thought. Clearly R knows whether you called the function by name or as a replacement (as the call stack is rather different) so this distinction would be easy enough to implement. – Matthew Lundberg Mar 13 '13 at 03:45