Search code examples
rvariable-assignmentinfix-operator

Assigning a value to a list item using `assign()`


A little bit of context first...

I've written an infix function that in essence replaces the idiom

x[[length(x) +1]] <- y

..or simply x <- append(x, y) for vectors.

Here it is:

`%+=%` <- function(x, y) {
  xcall <- substitute(x)
  xobjname <- setdiff(all.names(xcall), c("[[", "[", ":", "$"))
  # if the object doesn't exist, create it
  if (!exists(xobjname, parent.frame(), mode = "list") &&
      !exists(xobjname, parent.frame(), mode = "numeric") &&
      !exists(xobjname, parent.frame(), mode = "character")) {
    xobj <- subset(y, FALSE)
  } else {
    xobj <- eval(xcall, envir = parent.frame())
  }

  if (is.atomic(xobj)) {
    if (!is.atomic(y)) {
      stop('Cannot append object of mode ', dQuote(mode(y)), 
           ' to atomic structure ', xobjname)
    }
    assign(xobjname, append(xobj, y), envir = parent.frame())
    return(invisible())
  }

  if (is.list(xobj)) {
    if (is.atomic(y)) {
      xobj[[length(xobj) + 1]] <- y
    } else {
      for (i in seq_along(y)) {
        xobj[[length(xobj) + 1]] <- y[[i]]
        names(xobj)[length(xobj)] <- names(y[i])
      }
    }
    assign(xobjname, xobj, envir = parent.frame())
    return(invisible())
  }

  stop("Can't append to an object of mode ", 
       mode(eval(xcall, envir = parent.frame())))
}

It works as intended with vector or lists, but the limit in its present form is that I can't append a value to a item inside a list, e.g.:

a <- list(a = 1, b = 2)
a$b %+=% 3

So far I haven't found how to do it. I've tried something like the following, but it has no effect:

assign("b", append(a$b, 3), envir = as.environment(a))

Any ideas?


Solution

  • Suggest not using assign and instead:

    `%+=%`<- function(x, value) eval.parent(substitute(x <- append(x, value)))
    
    x <- 3
    x %+=% 5
    x
    ## [1] 3 5
    
    L <- list(a = 1, b = 2)
    L %+=% 3
    ## List of 3
    ## $ a: num 1
    ## $ b: num 2
    ## $  : num 3
    
    L <- list(a = 1, b = 2)
    L$a %+=% 4
    str(L)
    ## List of 2
    ##  $ a: num [1:2] 1 4
    ##  $ b: num 2
    

    or try +<- syntax which avoids the eval:

    `+<-` <- append
    
    # test
    x <- 3
    +x <- 1
    x
    ## [1] 3 1
    
    # test
    L<- list(a = 1, b = 2)
    +L <- 10
    str(L)
    ## List of 3
    ##  $ a: num 1
    ##  $ b: num 2
    ##  $  : num 10
    
    # test
    L <- list(a = 1, b = 2)
    +L$a <- 10
    str(L)
    ## List of 2
    ##  $ a: num [1:2] 1 10
    ##  $ b: num 2
    

    Or try this replacement function syntax which is similar to +<-.

    `append<-` <- append
    x <- 3
    append(x) <- 7
    ## [1] 3 7
    
    ... etc ...