2
\$\begingroup\$

I am writing an R package. The package consists of a main function and lots of auxiliary functions that perform different calculations.

The main function

The idea behind the package is to make the user use only the main function and through it select which auxiliary function to use. This would imply that only the main function is exported while the auxiliary functions are not. This is the way I am currently implementing the main function:

#' Main function
#' @export
#' 
#' Current solution. Note that the parameter fun is a character
main_function <- function(fun,a,b){
    if (fun == "square"){square(a,b)}
    else if (fun == "cube") {cube(a,b)}
    else {stop("Function not found")}
}

Auxiliary functions

Auxiliary functions take all the same two parameters.

## Auxiliary functions (not to be exported, to be used only by the main function: the user should not access these functions)
square <- function(a,b){a^2+b}
cube <- function(a,b){a^3+b}

This is how I am currently solving this problem, it works fine, however I have the feeling there must be a better way or some sort of "best practice" for such cases. Is there not? Perhaps using substitute of lazyeval. Sadly I couldn't use either. I would like to avoid using a character as an argument in the main function and perhaps use the full name of the function such as main_function(f1,2,3). Could you please hint me in other possible directions? I would like for this code to be as close as possible to the best practices involved in creating an R package.

\$\endgroup\$
1
\$\begingroup\$

You could use match.arg and/or match.fun. They are both pretty standard approaches.

  1. match.arg matches an input string to a pre-defined list. It allows for partial matches and handles the error in case it can't find a match.
  2. match.fun is like a smart get for doing "the right thing" depending on the input.

Using match.arg:

square <- function(a, b) sprintf("square(%s, %s)", a, b)
cube   <- function(a, b) sprintf("cube(%s, %s)", a, b)
other  <- function(a, b) sprintf("other(%s, %s)", a, b)

main <- function(a, b, fun = c("square", "cube"), ...) {
   fun <- match.arg(fun)

   # Then, you could do
   # switch(method, square = square(a, b, ...),
   #                cube   = cube(a, b, ...))
   # or use match.fun:

   fun <- match.fun(fun)
   fun(a = a, b = b, ...)
}

main(a = 1, b = 2, fun = "square")
# [1] "square(1, 2)"
main(a = 1, b = 2, fun = square)
# Error in match.arg(fun) : 'arg' must be NULL or a character vector 
main(a = 1, b = 2, fun = "cube")
# [1] "cube(1, 2)"
main(a = 1, b = 2, fun = "sq")  # partial matching
# [1] "square(1, 2)"
main(a = 1, b = 2)
# [1] "square(1, 2)"
main(a = 1, b = 2, fun = "other")
# Error in match.arg(fun) : 'arg' should be one of “square”, “cube”

Using match.fun:

main <- function(a, b, fun, ...) {
   print(fun)
   fun <- match.fun(fun)
   fun(a = a, b = b, ...)
}

main(a = 1, b = 2, fun = "square")
# [1] "square"
# [1] "square(1, 2)"
main(a = 1, b = 2, fun = `square`)
function(a, b) sprintf("square(%s, %s)", a, b)
# [1] "square(1, 2)"
main(a = 1, b = 2, fun = cube)
function(a, b) sprintf("cube(%s, %s)", a, b)
# [1] "cube(1, 2)"
main(a = 1, b = 2, fun = cub)   # no partial matching...
# Error in print(fun) : object 'cub' not found
main(a = 1, b = 2, fun = other)
# function(a, b) sprintf("other(%s, %s)", a, b)
# [1] "other(1, 2)"

As you can see, the two approaches behave a little differently as they may or may not:

  1. allow functions as inputs (e.g. square instead of "square")
  2. allow partial matching of function names
  3. easily restrict the functions that can be used

So it will be up to you to decide based on your requirements.

Note: I have added ... to all your functions in case, in the future, your functions allow differing arguments. As you mentioned, your functions currently all only require a and b so you can get rid of , ... everywhere if you wish.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.