dereckmezquita
dereckmezquita

Reputation: 41

Object destructuring in R for writing packages; like Python's `**` or JavaScript's `...`

I have been looking for a way to do object destructuring. This in an effort to write cleaner code when developing packages.

I often have a complex system of functions that call others etc and I want to make those arguments available at the higher level functions to the user.

When developing packages for R I end up often writing functions that call other functions and end up having to either use the ... operator (which can only be used once. Or to manually re-assign the object like so:

someFunction1 <- function(arg1, arg2) { print(stringr::str_interp('Do something with ${arg1}, and with ${arg2}')) }

someFunction2 <- function(arg3, arg4) { print(stringr::str_interp('Do something with ${arg1}, and with ${arg2}')) }

# solution with R's ...

someHigherFunction <- function(arg5, arg6, ...) {
    # do something with arg5 and 6

    someFunction1(...)
}

# otherwise I start having to do this; and the more nested the functions get the harder it is to manage
someHigherFunction <- function(arg5, arg6, arg1, arg2, arg3, arg4) {
    # do something with arg5 and 6

    someFunction1(arg1 = arg1, arg2 = arg2)
    someFunction1(arg3 = arg3, arg4 = arg4)
}

# there is a solution using do.call but it is rather annoying

someHigherFunction <- function(arg5, arg6, arg1_2 = list(arg1 = "something", arg2 = "something else")) {
    # do something with arg5 and 6

    # not ideal but it works
    do.call(someFunction1, arg1_2)
}

In JavaScript we can use object destructuring (easy for positional arguments), python has this too with it's own ** double star operator:

function foo(a, b) {
    console.log(a - b);
}

let args = [10, 7];

foo(...args);

There are other uses for this as well:

let a, b, rest;
[a, b] = [10, 20];

console.log(a);
// expected output: 10

console.log(b);
// expected output: 20

[a, b, ...rest] = [10, 20, 30, 40, 50];

console.log(rest);
// expected output: Array [30,40,50]

Does anyone have any suggestions on how to accomplish this in R? Did I miss something?

Upvotes: 1

Views: 298

Answers (1)

MorganK95
MorganK95

Reputation: 167

Expanding on above, is this pattern useful/generalizable?

someHigherFunction4 <- function(arg5, arg6, ...) {
  
  # collect arguments passed in dots
  
  args <- match.call(expand.dots = FALSE)$...
  
  # if arguments named, subset dots to avoid unused variable error in do.call
  
  if(!is.null(names(args))) {
    
    validArgs <- formalArgs(someFunction1)
    
    do.call(someFunction1, args[validArgs])
    
  } else {
    
  # if not, pass arguments by position
    
  do.call(someFunction1, as.list(args))

  }
}

someHigherFunction4("foo", TRUE, "this", "that")
# result: "Do something with this, and with that"

someHigherFunction4("foo", TRUE, arg1 = "this", arg2 = "that", arg3 = "bar")
# result: "Do something with this, and with that"

Upvotes: 1

Related Questions