Environments and lazy evaluation: getting rid of symbols and getting values

52 Views Asked by At

I am trying to wrap my head around a problem I ran into that I think largely revolves around environments, lazy evaluation and issues related to these things. I have a main function (foo1 below) that in my actual use case is quite extensive. For this reason I am trying to provide some wrapper functions (foo2 and foo3) that modify specific elements of the main function.

My problem is that I in the end don't obtain actual values, but I obtain symbols. Can someone provide some guidance as to what I need to with environments and what not to get this sorted?

Here's my MWE:

foo1 <- function(foo, a, b, c=1, d=1) {
  if (!missing(foo)) {
    mcall <- as.list(match.call())[-(1:2)]
    call_names <- names(mcall)
    foo <- foo[-which(names(foo) %in% call_names)]
    foo <- c(foo, mcall)
  } else {
    foo <- list(a=a, b=b, c=c, d=d)
  }
  return(foo)
}

foo2 <- function(a, b) {
  foo1(a=a, b=b)
}

foo3 <- function(foo, c, d) {
  foo1(foo, c=c, d=d)
}

f <- foo2(a=1, b=1) 
f <- foo3(f, c=2, d=2)
str(f)

> str(f)
List of 4
 $ a: num 1
 $ b: num 1
 $ c: symbol c
 $ d: symbol d

Here I want c and d to be equal to 2, but they are symbols.

1

There are 1 best solutions below

1
On BEST ANSWER

When you use match.call, it prevents evaulation of the parameters. If you want to evaluate the parameters, then you need to explicitly do so. For example

foo1 <- function(foo, a, b, c=1, d=1) {
  if (!missing(foo)) {
    mcall <- as.list(match.call())[-2]
    mcall[[1]] <- quote(list)
    mcall <- eval(as.call(mcall))
    call_names <- names(mcall)
    foo <- foo[-which(names(foo) %in% call_names)]
    foo <- c(foo, mcall)
  } else {
    foo <- list(a=a, b=b, c=c, d=d)
  }
  return(foo)
}

This turns the call to foo1 into a call to list() and evaluates the result. Another option would be just to use mget() with the variables in the local environment that you want to capture. This will trigger the evaulation

foo1 <- function(foo, a, b, c=1, d=1) {
  if (!missing(foo)) {
    mlist <- mget(intersect(setdiff(ls(), "foo"), names(match.call())))
    call_names <- names(mlist)
    foo <- foo[-which(names(foo) %in% call_names)]
    foo <- c(foo, mlist)
  } else {
    foo <- list(a=a, b=b, c=c, d=d)
  }
  return(foo)
}