0

I am writing a R equivalent to Pythons 'pop' method. I know 99th percentile has one but I'd prefer my own (practice/understanding/consistency etc). For reference, pop() takes an object and removes the first item from the object whilt also returning it. So

> l <- c(1,3,5)
> x <- pop(l)
> print(l)
> 3, 5
> print(x)
> 1

I am using assign() to replace the input object with one less the first value and returning said first value from the function.

My question is, how do I get the environment of the input object and use this environment within assign()? I have tried using pryr::where() which returns 'R_GlobalEnv' but I can't use this value in assign(). Instead the only value I can get to work in assign() is 'globalenv()'.

Posted from mobile so let me know if something doesn't work.

1
  • Are you looking for something like this R-Help post? Commented Aug 15, 2020 at 16:23

3 Answers 3

4

You can implement this in base R, though it's not advised. R is a functional language and functions with side effects are not expected by end-users.

pop <- function(vec)
{
  vec_name <- deparse(substitute(vec))
  assign(vec_name, vec[-1], envir = parent.frame())
  vec[1]
}

a <- c(2, 7, 9)

a
#> [1] 2 7 9

pop(a)
#> [1] 2

a
#> [1] 7 9

pop(a)
#> [1] 7

a
#> [1] 9

Created on 2020-08-15 by the reprex package (v0.3.0)

Sign up to request clarification or add additional context in comments.

2 Comments

I've just edited my answer: I'd be interested if you see a way to handle the indirect pop I describe there.
Marked as correct because it gets the env I was after. You are correct, this isn't what a user would expect from functional R, this however is being used in a program which is has a R6 OOP framework. A message is added letting the user know the original object has been changed. Thanks!
2

The following answer is based in this R-Help post, function pop with function getEnvOf from this SO post, both adapted to the question's problem.

getEnvOf <- function(what, which=rev(sys.parents())) {
  what <- as.character(substitute(what))
  for (frame in which)
    if (exists(what, frame=frame, inherits=FALSE))
      return(sys.frame(frame))
  return(NULL)
}
pop <- function(x){
  y <- as.character(substitute(x))
  e <- getEnvOf(y)
  if(length(x) > 0) {
    val <- x[[length(x)]]
    assign(y, x[-length(x)], envir = parent.env(e))
    val
  } else {
    msg <- paste(sQuote(y), "length is not > 0")
    warning(msg)
    NULL
  }
}

y <- c(1,3,5)
pop(y)

This also works with lists.

z <- list(1, 2, 5)
pop(z)

w <- list(1, c(2, 4, 6), 5)
pop(w)
#[1] 5

pop(w)
#[1] 2 4 6

pop(w)
#[1] 1

pop(w)
#NULL
#Warning message:
#In pop(w) : ‘w’ length is not > 0

1 Comment

I've just edited my answer: I'd be interested if you see a way to handle the indirect pop I describe there.
2

You can do it using pryr::promise_info(l)$env, but it's a very un-R-like thing to do. Functions shouldn't have side effects.

For example,

    pop <- function(l) {
      info <- pryr::promise_info(l)
      if (!is.name(info$code))
        stop("Argument expression should be a name.")
      result <- l[[1]]  # work on lists too
      assign(as.character(info$code), l[-1], envir = info$env)
      result
    }
    l <- c(1, 3, 5)
    pop(l)
#> Registered S3 method overwritten by 'pryr':
#>   method      from
#>   print.bytes Rcpp
#> [1] 1
    l
#> [1] 3 5

Created on 2020-08-15 by the reprex package (v0.3.0)

Edited to add: Interestingly, none of the three answers so far works in complicated situations like this one:

f <- function(x) {
  cat("The pop(x) result is", pop(x), "\n")
  cat("Now x is ", x, "\n")
  cat("Now l is ", l, "\n")
}

l <- c(1, 3, 5)
f(l)

@RuiBarradas's answer gives

The pop(x) result is 5 
Now x is  1 3 5 
Now l is  1 3 5 

(He pops the last value rather than the first which is not a big deal, but neither x nor l is modified.)

@AllanCameron's answer gives

The pop(x) result is 1 
Now x is  3 5 
Now l is  1 3 5 

This is arguably correct (x got popped), but I think it would be nice to have l being popped, and that seems tricky.

My answer dies with this message:

Error in pop(x) : Argument expression should be a name.

which seems like a bug: obviously whether it's getting x or l, it really is a name. The problem seems to be in pryr::promise_info, which returns the compiled code that would return the value of x, rather than just the code for x. If I turn off JIT compiling by compiler::enableJIT(0), I get the same result as @AllanCameron. It's not clear to me how to unwind back the right amount to pop l instead of just x.

4 Comments

I don't think I'd be too happy about function f popping the original l. What if we wanted a function that used pop as part of some algorithm to process a vector to generate a new result from it? We would wreck our initial vector. We couldn't pass a data frame column to it, for example. That's why I wouldn't use pop, and if I did, it would only be for internal use and wouldn't overwrite anything higher than the first parent frame. So I think the behaviour you show is desired and correct. It's a feature, not a bug ;)
Obviously the name f isn't very informative, but what if it was a function called showThenPop, that printed its argument before popping? In that case you'd want the pop to apply to l, but you couldn't do it with our implementations of the pop function, you'd need to copy the body of pop into showThenPop. Basically pop is all about passing by reference, and it seems hard to fake passing by reference two levels down.
If you really wanted to do that, you could just use the same trick in the outer function of rewriting x to l in the parent frame. This again allows you to limit your actions to the parent frame. There just isn't a need to make it any more complicated.
This is really useful for anyone else coming to this, I've marked another answer as correct because it provided a solution to my specific situation, but as mentioned, its not for functional use. Having this here so others can see the problem is really useful, thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.