Get name of x when defining `(<-` operator
Asked Answered
A

2

5

I want to define (<- and access the name of the left hand side argument :

*<- functions use internally an intermediate '*tmp*' variable. Is it still possible to get the name of x ?

`(<-` <- function(x,value){
  print(deparse(substitute(value)))
  print(deparse(substitute(x)))
  print(match.call())
  value
}

foo <- 0
(foo) <- 3
# [1] "3"
# [1] "*tmp*"
# `(<-`(x = `*tmp*`, value = 3)# [1] "3"

I want to get "foo" from inside the function.

I tried to hack it by using tracemem, i.e. calling sapply(ls(envir = parent.frame()),tracemem) and tracemem(x) inside of the functions but the address of foo, *temp* and x are all different.

Anna answered 9/7, 2018 at 13:12 Comment(0)
A
4

I hacked it, though I didn't understand everything that I did.

I noticed pryr::address was giving a different kind of results than tracemem and tried it (I had to dig into the code to use pryr:::address2 because pryr::address doesn't have an environment argument).

Then I noticed that mixing the results from tracemem on x and pryr:::address2 on the rest of the objects there was a match (after basic reformatting) :

`(<-` <- function(x,value){
  pf <- parent.frame()
  all_addresses       <- sapply(ls(pf), pryr:::address2, pf)
  all_addresses       <- all_addresses[names(all_addresses) != "*tmp*"]
  all_addresses_short <- gsub("(^|<)[0x]*(.*?)(>|$)","\\2",all_addresses)

  x_address       <- tracemem(x)
  x_address_short <- tolower(gsub("(^|<)[0x]*(.*?)(>|$)","\\2",x_address))

  ind    <- match(x_address_short, all_addresses_short)
  x_name <- names(all_addresses)[ind]

  message("all_addresses, using pryr::address2")
  print(all_addresses)
  print(all_addresses_short)

  message("x_address, using tracemem")
  print(x_address)
  print(x_address_short)

  message("x_name, matching substrings")
  print(x_name)

  value
}

The regex used in gsub calls tries to account for the address formats we get with different systems, I'm not 100% sure that it's general.

output:

foo <- 1
bar <- 2
(foo) <- foo

# all_addresses, using pryr::address2
# (<-          bar          foo 
# "0x1433df50" "0x14937678" "0x14937708" 
# (<-        bar        foo 
# "1433df50" "14937678" "14937708" 
# x_address, using tracemem
# [1] "<0000000014937708>"
# [1] "14937708"
# x_name, matching substrings
# [1] "foo"

It breaks if x is not a variable name, for example:

foo <- iris
(foo$species) <- 3

We could assume that if the address isn't found x is a list item, and then lookup its address among the addresses of the items of all the lists we have in the parent.frame (recursively), but I think that's enough ugly hacks for today.

Anna answered 9/7, 2018 at 14:7 Comment(0)
M
4

1) If you are willing to change it so that the call is:

fooify[foo] <- 99

then we can do it like this where foo need not exist beforehand:

fooify <- structure(NA, class = "fooify")
"[<-.fooify" <- function(x, var, value) {
  print(deparse(substitute(var)))
  eval.parent(substitute(var <- value))
  x
}

# test

if (exists("foo")) rm(foo)
fooify[foo] <- 99
## [1] "foo"  <-- this comes from the print statement
foo
## [1] 99

2) := If using := is ok then:

`:=` <- function(lhs, rhs) {
  print(deparse(substitute(lhs)))
  eval.parent(substitute(lhs <- rhs))
}

# test
if (exists("foo")) rm(foo)
foo := 99
## [1] foo   <-- this comes from print statement
foo
## [1] 99
Matrona answered 9/7, 2018 at 18:20 Comment(4)
That is cleaner indeed, and i really like that the object doesn't need to exist. the main problem is that it really looks like you're assigning a value to an item of object fooify when my goal is to do an assignment with side effects, but I think it might be acceptable if name fooify . or ._Anna
Have added second approach.Matrona
great! I didn't think about this operator! or assumed I'd have the same issue with *temp*, it won't clash with data.table or tidyverse if I use it out of their functions right ?Anna
It's possible you may have conflicts. dplyr and tidyr don't actually load := on the search path even though they allow its use so you should be OK there but if you load rlang you could have a conflict. I tried running data.table with := in the global environment and it complained of a conflict when I loaded data.table but everything still worked in both data.table and with our own := that I tried. Nevertheless #1 in the answer would be safer if you are concerned about such conflicts.Matrona

© 2022 - 2024 — McMap. All rights reserved.