Bryan Hanson
Bryan Hanson

Reputation: 6213

Intercepting & using the value of an optional variable captured in the dots (...)

I need to intercept the value of an optional xlim in a function so that I can change the units of it before plotting. The following function confirms that xlim was passed, but I can't access the value.

foo <- function(x, y, ...) {
    if ("xlim" %in% names(list(...))) {
        print(xlim) # not found/can't use value!
        }
    # modify xlim and pass to plotting functions
    return()
    }

But foo(x = 1:5, y = 1:5, xlim = c(2,4)) gives:

Error in print(xlim) : object 'xlim' not found

What trick do I need use the value? Seems like it should just work, but I see from looking around on SO that the dots can be vexing. I've played a bit with exists, deparse etc but I don't really 'get' the proper use of those functions.

EDIT: so here is the final snippet which was the leanest way to access the value:

dots <- list(...)
if (any(names(dots) == "xlim")) {
    xlim <- dots$xlim
    print(xlim)
    }

Upvotes: 4

Views: 77

Answers (1)

Rich Scriven
Rich Scriven

Reputation: 99331

This is because xlim is actually a list element, and is not (yet) an actual object in the function's environment. You could do

foo <- function(x, y, ...) {
    m <- match.call(expand.dots = FALSE)$...
    if(any(names(m) == "xlim")) m[["xlim"]]
    else stop("no xlim value")
}
foo(x = 1:5, y = 1:5, xlim = c(2,4))
# c(2, 4)
foo(x = 1:5, y = 1:5, ylim = c(2,4))
# Error in foo(x = 1:5, y = 1:5, ylim = c(2, 4)) : no xlim value

You can see what match.call is doing if we examine the function as

f <- function(x, y, ...) {
    match.call(expand.dots = FALSE)$...
}

It is a list of all the entered dot arguments with their respective expressions, so there are many different ways to get the values, the above is just one way.

f(x = 1:5, y = 1:5, xlim = c(2,4))
# $xlim
# c(2, 4)

Alternatively, you could do

g <- function(x, y, ...) {
    dots <- list(...)
    any(names(dots) == "xlim")
}
g(x = 1:5, y = 1:5, xlim = c(2,4))
# [1] TRUE

Also keep in mind that match.call keeps the argument as an unevaluated call, while list(...) evaluates the argument. This might be important for you passing the argument to other functions.

Upvotes: 2

Related Questions