Skip to content

Instantly share code, notes, and snippets.

@gmbecker
Created April 4, 2018 18:21
Show Gist options
  • Save gmbecker/c0e67645dd1e1a5262964a4728c79e61 to your computer and use it in GitHub Desktop.
Save gmbecker/c0e67645dd1e1a5262964a4728c79e61 to your computer and use it in GitHub Desktop.
Add argument specification to a function call
argify = function(expr, recursive = FALSE) {
if(is.character(expr))
expr = parse(text = expr)
else if(!is.call(expr))
expr = substitute(expr)
stopifnot(is.call(expr))
if(recursive && length(expr) > 1) {
for(i in 2:length(expr)) {
if(is.call(expr[[i]]))
expr[[i]] = argify(expr[[i]], TRUE)
}
}
fname = as.character(expr[[1]]) #name of function called
reffun = get(fname)
fun = function() { match.call()}
formals(fun) = formals(reffun)
assign(fname, fun)
eval(expr)
}
foo = function(arg1, arg2, arg3, arg4) NULL
bar = function(bar1, bar2) NULL
argify(foo(1, cbind(3, bar(3, 4)), "a", rnorm))
argify(foo(1, cbind(3, bar(3, 4)), "a", rnorm), recursive= TRUE)
@HenrikBengtsson
Copy link

Thanks. Some minor bumps in the road:

> argify(rnorm(dummy))
Error in rnorm(dummy) : object 'dummy' not found

# Workaround
> argify(quote(rnorm(dummy)))
rnorm(n = dummy)

Not important, but maybe add argify(..., substitute = TRUE) that does if (substitute) expr <- substitute(expr)?

> argify("rnorm(dummy)")
Error: is.call(expr) is not TRUE

## Because parse():d expression is not a call
> expr <- parse(text = "rnorm(dummy)")
> is.call(expr)
[1] FALSE
> is.language(expr)
[1] TRUE

@HenrikBengtsson
Copy link

A tweaked version that fixes argify(<string>) bug:

argify = function(expr, recursive = FALSE) {
    if(is.character(expr)) {
        stopifnot(length(expr) == 1)
        expr = parse(text = expr)[[1]]
    } else if(!is.call(expr))
        expr = substitute(expr)
    stopifnot(is.language(expr))
    if(recursive && length(expr) > 1) {
        for(i in 2:length(expr)) {
            if(is.call(expr[[i]]))
                expr[[i]] = argify(expr[[i]], TRUE)
        }
    }
    fname = as.character(expr[[1]]) #name of function called
    reffun = get(fname)
    fun = function() { match.call()}
    formals(fun) = formals(reffun)
    assign(fname, fun)
    eval(expr)
}

@klmr
Copy link

klmr commented Apr 4, 2018

    reffun = get(fname)

This should use match.fun (or get(…, mode = 'function')); get alone can fail if the function is shadowed by a (non-function) variable.

… but isn’t the function needlessly complicated anyway? The last four lines of the function (L15–L18) can be replaced by a single

match.call(reffun, expr)

No need to inject the call into the original expression.

@gmbecker
Copy link
Author

gmbecker commented Apr 4, 2018

@klmr I think that's right. I had a bunch of balls in the air in terms of what I Was doing with the expression but given where it landed what you said is the better approach for that part.

@HenrikBengtsson you're right, thanks. The need for the [[1]] slipped my mind there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment