Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to retrieve formals of a primitive function?

Tags:

r

For the moment, at least, this is an exercise in learning for me, so the actual functions or their complexity is not the issue. Suppose I write a function whose argument list includes some input variables and a function name, passed as a string. This function then calculates some variables internally and "decides" how to feed them to the function name I've passed in.

For nonprimitive functions, I can do (for this example, assume non of my funcname functions have any arguments other than at most (x,y,z). If they did, I'd have to write some code to search for matching names(formals(get(funcname))) so as not to delete the other arguments):

foo <- function (a,b,funcname) {
    x <- 2*a
    y <- a+3*b
     z <- -b
    formals(get(funcname)) <- list(x=x, y=y, z=z)
    bar <- get(funcname)()
return(bar)
}

And the nice thing is, even if the function funcname will execute without error even if it doesn't use x, y or z (so long as there are no other args that don't have defaults) .
The problem with "primitive" functions is I don't know any way to find or modify their formals. Other than writing a wrapper, e.g. foosin <-function(x) sin(x), is there a way to set up my foo function to work with both primitive and nonprimitive function names as input arguments?

like image 671
Carl Witthoft Avatar asked Sep 22 '14 15:09

Carl Witthoft


2 Answers

formals(args(FUN)) can be used to get the formals of a primitive function.

You could add an if statement to your existing function.

> formals(sum)
# NULL
> foo2 <- function(x) {
      if(is.primitive(x)) formals(args(x)) else formals(x)
      ## formals(if(is.primitive(x)) args(x) else x) is another option
  }
> foo2(sum)
# $...
#
#
# $na.rm
# [1] FALSE
#
> foo2(with)
# $data
# 
#
# $expr
#
#
# $...
like image 92
Rich Scriven Avatar answered Oct 08 '22 10:10

Rich Scriven


Building on Richard S' response, I ended up doing the following. Posted just in case anyone else ever tries do things as weird as I do.

EDIT: I think more type-checking needs to be done. It's possible that coleqn could be the name of an object, in which case get(coleqn) will return some data. Probably I need to add a if(is.function(rab)) right after the if(!is.null(rab)). (Of course, given that I wrote the function for my own needs, if I was stupid enough to pass an object, I deserve what I get :-) ).

# "coleqn" is the input argument, which is a string that could be either a function
# name or an expression.

rab<-tryCatch(get(coleqn),error=function(x) {} )
#oops, rab can easily be neither NULL nor a closure. Damn.
if(!is.null(rab)) {
# I believe this means it must be a function
# thanks to Richard Scriven of SO for this fix to handle primitives
# we are not allowed to redefine primitive's formals.
    qq <- list(x=x,y=y,z=z) 
#  matchup the actual formals names
#  by building a list of valid arguments to pass to do.call 
    argk<-NULL
    argnames<-names(formals(args(coleqn)))
    for(j in 1:length(argnames) )
        argk[j]<-which(names(qq)==argnames[1] )
    arglist<-list()
    for(j in 1:length(qq) ) 
        if(!is.na(argk[j])) arglist[[names(qq)[j]]]<-qq[[j]]
    colvar<- do.call(coleqn,arglist) 
    } else {
# the input is just an expression (string), not a function
        colvar <- eval(parse(text=coleqn)) 
    } 

The result is an object generated either by the expression or the function just created, using variables internal to the main function (which is not shown in this snippet)

like image 42
Carl Witthoft Avatar answered Oct 08 '22 08:10

Carl Witthoft