Passing missing argument from function to function in R
Asked Answered
C

4

20

I’ve learned that it’s common practice to use optional arguments in function and check them with missing() (e.g. as discussed in SO 22024082)

In this example round0 is the optional argument (I know, round0 could be defined as logical).

foo = function(a, round0) {
    a = a * pi
    if(!missing(round0)) round(a)
    else a
}

But what if I call this function from another function, how can I pass “missing”?

bar = function(b) {
    if(b > 10) round1=T
    foo(b, round1)
}

If b < 10 then round1 in bar() is not defined, but is passed to foo anyway. If I modify foo():

foo = function(a, round0) {
    a = a * pi
    print(missing(round0))
    print(round0)
    if(!missing(round0)) round(a)
    else a
}

and run bar(9) the output is:

bar(9)
[1] FALSE
Error in print(round0) : object 'round1' not found
Called from: print(round0)

That means: round0 is not missing, but can’t be accessed either?

I don’t want to use different function calls in bar(), if there are several optional arguments in foo(), I would have to write a function call for every missing/not missing - combination of all optional arguments.

Is it possible to pass "missing", or what other solution would apply for this problem?

Chronometry answered 22/7, 2015 at 8:27 Comment(2)
Usually, if you call a function in specific form, you should ensure you have all the parameters. Here I would add a round1=F at start of bar and update foo with if(!missing(round0) && round0). Missing allow you to call foo(a) or foo(a,T/F), if you call it with two parameters, the second parameter is not missing and have to be resolvable.Pastelki
My guess is that missing returns FALSE as soon as the promise object that represents the function argument has a non-empty expression slot. Add the line print(substitute(round0)) right after a = a * pi in your modified foo function and then execute foo(9). substitute will extract the expression slot. It will print nothing, i.e., an empty expression slot for round0. Now try bar(9). This prints round1. But when you use print, R will try to evaluate round1 which was not defined yet (lazy evaluation).Lowpitched
N
9

In your example, round0 isn't missing, it's set to round1 which is undefined (as opposed to missing).

The best way in general of doing this is to use a default value, in your case FALSE:

foo = function(a, round0 = FALSE) {
    a = a * pi
    if (!round0) round(a)
    else a
}

bar = function(b) {
    round1 <- FALSE
    if (b > 10) round1=TRUE
    foo(b, round1)
}

or where the default value cannot easily be expressed in the parameter list:

foo = function(a, round0 = NULL) {
    a = a * pi
    if(!is.null(round0)) round(a)
    else a
}

bar = function(b) {
    round1 <- NULL
    if (b > 10) round1=TRUE
    foo(b, round1)
}

Note in both cases you need to set the parameter to be the default value manually in your calling function.

You could also call your foo function with or without an argument if needed within your if statement:

bar = function(b) {
    if (b > 10) foo(b, TRUE) else foo(b)
}

An alternative approach that shows how to generate a missing value is shown by @moody_mudskipper’s answer.

Nitrate answered 22/7, 2015 at 8:47 Comment(2)
The last statement is inaccurate. You can make a variable missing by defining it as quote(expr=), substitute() or alist(x=1)[[1]]. The former is the most efficient. {rlang} has a missing_arg function that does the same too. See my own answer below.Ocular
@Moody_Mudskipper thanks, I didn’t know that. I’ve modified my answer to point to yours.Nitrate
U
9

I recently encountered a similar problem and wanted to solve it in a general way. I think it can be done as shown in the definition of the function g() below:

f <- function(a = 5, b = 3, c = 2, d = 7) {
  if (missing(a)) {print("a is missing.")}
  if (missing(b)) {print("b is missing.")}
  if (missing(c)) {print("c is missing.")}
  if (missing(d)) {print("d is missing.")}

  cat(a, b, c, d)
}

g <- function(a = 1, b = 1, c = 1, d = 1) {
  args <- as.list(match.call())
  args[[1]] <- NULL # remove first list element, it's the function call
  do.call(f, args, envir = parent.frame())
}

Here is what we get when calling g() with different sets of arguments:

> g()
[1] "a is missing."
[1] "b is missing."
[1] "c is missing."
[1] "d is missing."
5 3 2 7

> g(a = 3)
[1] "b is missing."
[1] "c is missing."
[1] "d is missing."
3 3 2 7

> g(b = 10, c = 10)
[1] "a is missing."
[1] "d is missing."
5 10 10 7

You can add to or remove from the args list if you don't want to hand all arguments to the next function or want to add some. As an example, see the following function g() that does this in a general way:

g <- function(a = 1, b = 1, c = 1, x = 1, y = 1, z = 1) {
  f_args <- c("a", "b", "c") # arguments we want to hand off to function f

  # obtain the list of arguments provided
  args <- as.list(match.call())
  # remove first list element, it's the function call
  args[[1]] <- NULL
  # remove the arguments that are not listed in f_args
  args <- args[na.omit(match(f_args, names(args)))]

  # now add argument d, we always want it to be 0:
  args <- c(args, list(d = 0))
  do.call(f, args, envir = parent.frame())
}

Here is what we get when calling g() with different sets of arguments:

> g()
[1] "a is missing."
[1] "b is missing."
[1] "c is missing."
5 3 2 0

> g(a = 3)
[1] "b is missing."
[1] "c is missing."
3 3 2 0

> g(b = 10, c = 10)
[1] "a is missing."
5 10 10 0

See this answer for additional information on do.call().

Unhouse answered 18/9, 2017 at 23:34 Comment(0)
O
4

You can create a missing object by using substitute() without argument.

In your case we could make round1 a missing object in the else clause :

foo = function(a, round0) {
  a = a * pi
  if(!missing(round0)) round(a)
  else a
}

bar = function(b) {
  if(b > 10) round1=T else round1 <- substitute()
  foo(b, round1)
}

bar(9)
#> [1] 28.27433

Created on 2019-10-24 by the reprex package (v0.3.0)

Ocular answered 24/10, 2019 at 15:36 Comment(1)
missing() needs it's input to be something (its name is an oxymoron in this regard). This something is an empty expression, and it appears substitute() can create some, you could also do alist(x=)[[1]]. For the long answer we'd need to get into the C implementation and I don't know much about it.Ocular
I
1

rlang provides also a function missing_arg() that makes an argument be missing.

foo = function(a, round0) {
  a = a * pi
  if(!missing(round0)) round(a)
  else a
}

bar = function(b) {
  if(b > 10) round1 <- TRUE else round1 <- rlang::missing_arg()
  foo(b, round1)
}

foo(9)
#> [1] 28.27433
bar(9)
#> [1] 28.27433

Created on 2020-12-02 by the reprex package (v0.3.0)

Indoors answered 3/12, 2020 at 3:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.