3

I have been playing around with R6 ab bit and tried to implement a replacement function (similar in spirit to base::`diag<-`()). I wasn't hugely surprised to learn that the following does not work

library(R6)

r6_class <- R6Class("r6_class",
  public = list(
    initialize = function(x) private$data <- x,
    elem = function(i) private$data[i],
    `elem<-` = function(i, val) private$data[i] <- val
  ),
  private = list(
    data = NULL
  )
)

test <- r6_class$new(1:5)
test$elem(2)
#> [1] 2
test$elem(2) <- 3
#> Error in test$elem(2) <- 3 :
#>  target of assignment expands to non-language object

What does this correspond to in prefix notation? All of the following work as expected, so I guess it's none of these

test$`elem<-`(2, 3)
`$`(test, "elem<-")(2, 3)

I'm less interested in possible workarounds, but more in understanding why the above is invalid.

nbenn
  • 591
  • 4
  • 12
  • I think the corresponding prefix notation would be `\`<-\`(\`$\`(test, "elem")(2), 3)`. At least it gives the same error. – MrFlick Aug 22 '19 at 18:15
  • 1
    That error seems to be coming from the [`evalseq` function](https://github.com/wch/r-source/blob/05c6fd1e7a029cde6d204425416e0cb1f960587a/src/main/eval.c#L2429). The evaulator seems to be unable to to figure out which assign function to use. I think the problem is the `$` operator interferes with the signature of the `elem<-` method. – MrFlick Aug 22 '19 at 18:53
  • 1
    @MrFlick thanks for the `evalseq` reference. I'll try to look into that. The intro comment `This is the stuff that nightmares are made of` is not very encouraging though. – nbenn Aug 23 '19 at 09:26

1 Answers1

0

You are allowed to have nested complex assignments, e.g.

names(x)[3] <- "c"

but

test$elem(2) <- 3

is not of that form. It would be legal syntax as

elem(test,2) <- 3

which would expand to

*tmp* <- test
test <- `elem<-`(*tmp*, 2, 3)

but in the original form it would have to expand to

*tmp* <- 2
2 <- `test$elem<-`(*tmp*, 3)

(I've used test$elem<- in backticks to suggest it's the assignment version of the function returned by test$elem. That's not really right, there is no such thing.) The main problem is that the object being modified is 2, so you get the error message you saw: you're not allowed to modify 2.

If you want to do this in R6, I think you could do it something like this. Define a global function

`elem<-` <- function(x, arg, value) x$`elem<-`(arg, value)

and change the definition of your class elem<- method to

`elem<-` = function(i, val) { private$data[i] <- val; self }

Not all that convenient to need two definitions for every assignment method, but it appears to work.

user2554330
  • 37,248
  • 4
  • 43
  • 90
  • @ user2554330 how do you figure that `test$elem(2) <- 3` is expanded to ``2 <- `test$elem<-`(*tmp*, 3)``? I'm afraid I don't follow. This is in contradiction with what @MrFlick says in his comment, no? – nbenn Aug 23 '19 at 09:24
  • No, not really. In prefix notation the expression is what he said (except `elem` shouldn't be in quotes). The expansion happens early in evaluation, when the `<-` function is being evaluated. If its first argument is a function call, then the complex assignment version is called. The first argument is a function call, but the function is itself the result of a function call, `test$elem`. The point is that the 1st argument of that function is the object being modified, and in your case that's `2`. – user2554330 Aug 23 '19 at 12:50