ISO a good way to let a function accept a mix of supplied arguments, arguments from a list, and defaults

python pass function as argument with parameters
python optional arguments
python function arguments
python default arguments
python kwargs
python 3 function arguments
function without argument in python
python positional argument after keyword argument

I would like to have a function accept arguments in the usual R way, most of which will have defaults. But I would also like it to accept a list of named arguments corresponding to some or some or all of the formals. Finally, I would like arguments supplied to the function directly, and not through the list, to override the list arguments where they conflict.

I could do this with a bunch of nested if-statements. But I have a feeling there is some elegant, concise, R-ish programming-on-the-language solution -- probably multiple such solutions -- and I would like to learn to use them. To show the kind of solution I am looking for:

> arg_lst <- list(x=0, y=1)
> fn <- function(a_list = NULL, x=2, y=3, z=5, ...){
   <missing code>
   print(c(x, y, z))
  }

> fn(a_list = arg_list, y=7)

Desired output:

x  y  z
0  7  5

I like a lot about @jdobres's approach, but I don't like the use of assign and the potential scoping breaks.

I also don't like the premise, that a function should be written in a special way for this to work. Wouldn't it be better to write a wrapper, much like do.call, to work this way with any function? Here is that approach:

Edit: solution based off of purrr::invoke

Thinking a bit more about this, purrr::invoke almost get's there - but it will result in an error if a list argument is also passed to .... But we can make slight modifications to the code and get a working version more concisely. This version seems more robust.

library(purrr)
h_invoke = function (.f, .x = NULL, ..., .env = NULL) {
    .env <- .env %||% parent.frame()
    args <- c(list(...), as.list(.x))  # switch order so ... is first
    args = args[!duplicated(names(args))] # remove duplicates
    do.call(.f, args, envir = .env)
}

h_invoke(fn, arg_list, y = 7)
# [1] 0 7 5

Original version borrowing heavily from jdobres's code:

hierarchical_do_call = function(f, a_list = NULL, ...){
   formal_args = formals() # get the function's defined inputs and defaults
   formal_args[names(formal_args) %in% c('f', 'a_list', '...')] = NULL # remove these two from formals
   supplied_args <- as.list(match.call())[-1] # get the supplied arguments
   supplied_args[c('f', 'a_list')] = NULL # ...but remove the argument list and the function
   a_list[names(supplied_args)] = supplied_args
   do.call(what = f, args = a_list)
}

fn = function(x=2, y=3, z=5) {
  print(c(x, y, z))
}

arg_list <- list(x=0, y=1)
hierarchical_do_call(f = fn, a_list = arg_list, y=7)
# x  y  z
# 0  7  5

Parameters and Arguments, It includes information about how to pass by reference, and how to define and Parameters of methods can be specified as optional and given a default value. Parameter Patterns. Parameters supplied to functions and methods are, in general, Methods usually use the tuple form of passing arguments. This function requires one argument as input. Of course, the caller might not provide a value for param, which means that param is undefined. You can check for that issue by using the typeof operator. Notice that when param is undefined, the code assigns it a default value. This is the correct way to handle optional named arguments.

I'm not sure how "elegant" this is, but here's my best attempt to satisfy the OP's requirements. The if/else logic is actually pretty straightforward (no nesting needed, per se). The real work is in collecting and sanitizing the three different input types (formal defaults, the list object, and any supplied arguments).

fn <- function(a_list = NULL, x = 2, y = 3, z = 5, ...) {

  formal_args <- formals() # get the function's defined inputs and defaults
  formal_args[names(formal_args) %in% c('a_list', '...')] <- NULL # remove these two from formals
  supplied_args <- as.list(match.call())[-1] # get the supplied arguments
  supplied_args['a_list'] <- NULL # ...but remove the argument list

  # for each uniquely named item among the 3 inputs (argument list, defaults, and supplied args):
  for (i in unique(c(names(a_list), names(formal_args), names(supplied_args)))) {
    if (!is.null(supplied_args[[i]])) {
      assign(i, supplied_args[[i]])
    } else if (!is.null(a_list[[i]])) {
      assign(i, a_list[[i]])
    }
  }

    print(c(x, y, z))
}

arg_lst <- list(x = 0, y = 1)
fn(a_list = arg_lst, y=7)

[1] 0 7 5

With a little more digging into R's meta-programming functions, it's actually possible to pack this hierarchical assignment into its own function, which is designed to operate on the function environment that called it. This makes it easier to reuse this functionality, but it definitely breaks scope and should be considered dangerous.

The "hierarchical assignment" function, mostly the same as before:

hierarchical_assign <- function(a_list) {

  formal_args <- formals(sys.function(-1)) # get the function's defined inputs and defaults
  formal_args[names(formal_args) %in% c('a_list', '...')] <- NULL # remove these two from formals
  supplied_args <- as.list(match.call(sys.function(-1), sys.call(-1)))[-1] # get the supplied arguments
  supplied_args['a_list'] <- NULL # ...but remove the argument list

  # for each uniquely named item among the 3 inputs (argument list, defaults, and supplied args):
  for (i in unique(c(names(a_list), names(formal_args), names(supplied_args)))) {
    if (!is.null(supplied_args[[i]])) {
      assign(i, supplied_args[[i]], envir = parent.frame())
    } else if (!is.null(a_list[[i]])) {
      assign(i, a_list[[i]], envir = parent.frame())
    }
  }
}

And the usage. Note that the the calling function must have an argument named a_list, and it must be passed to hierarchical_assign.

fn <- function(a_list = NULL, x = 2, y = 3, z = 5, ...) {

    hierarchical_assign(a_list)

    print(c(x, y, z))
}

[1] 0 7 5

[PDF] Keyword and Optional Arguments in PLT Scheme, by position, as well as optional arguments with default values. Un- like previous that accepts a list of keywords for supplied arguments, a parallel list of values  Variable Function Arguments. Up until now, functions had a fixed number of arguments. In Python, there are other ways to define a function that can take variable number of arguments. Three different forms of this type are described below. Python Default Arguments. Function arguments can have default values in Python.

I think do.call() does exactly what you want. It accepts a function and a list as arguments, the list being arguments for the functions. I think you will need a wrapper function to create this behavior of "overwriting defaults"

Argument Passing - Learning Python [Book], Argument Passing Let's expand on the notion of argument passing in Python. (​the list called L in the caller); the result of the assignment to y[0] in the function that alter the way the argument objects in the call are paired with argument names By default, they are matched by position, from left to right, and you must pass  function argument passing. they are defined and initialized by the arguments passed in the function call Passing an argument by value works the exact same way.

C++ Core Guidelines, By “modern C++” we mean effective use of the ISO C++ standard (currently C++​17, but show how possible checks are avoided by interfaces that pass Both let the caller know that a function will not modify the argument, and both Flag any lambda capture-list that specifies a default capture and also  You must declare the name of a function as a module-level variable if you want to create a function. False You can use _______________ as your project executes in order to view the values of variables and control contents that are referenced in the current statement and a few statements on either side of the current statement.

Constructors, C++ FAQ, How should initializers be ordered in a constructor's initialization list? Is it moral for Then function f() declares a local List object called x : Let's work an example. A “default constructor” is a constructor that can be called with no arguments. Immediate base classes (left to right), then member objects (top to bottom). True/False: To account for the null terminator stored at the end of each C-string, the strlen function returns the number of characters in its argument, plus one. False True/False: The isdigit function will return a nonzero value if its argument is a digit.

Named Arguments and Default Values, When arguments are passed to a function without a name, Python assumes sign, to let the function know exactly which argument you are passing to the function. To supply a default value when defining a function, you use a syntax similar to the way you use named arguments when calling a function,  In mathematics, an argument of a function is a value that must be provided to obtain the function's result. It is also called an independent variable.. For example, the binary function (,) = + has two arguments, and , in an ordered pair (,).

Comments
  • Very nice! My own answer felt a bit ugly to me. Slapping my forehead for not realizing it’d be more sane to do it your way, with the function passed as an arg in a wrapper.
  • When I look at code from a first-rate programmer, this is what I see: clean, simple, abstract. <sigh> Well, I'll never write poetry like ee cummings, either. At least I have the eye to appreciate it when I see it.
  • Nice answer! I borrowed a lot of code in my answer, hope you don't mind.
  • I think do.cal does it so long as I write arguments in both the interior and the exterior function. But the whole reason for the hierarchical structure where the list dominates the defaults but is dominated by individual arguments is so that you don't have to reach inside the function. I don't see an obvious way to do this at the outer level with either a wrapper to do.call or something in the wrapped function or both. I'm not saying it is impossible -- just that I don't know how to do it.