Could someone explain to me why using a function call as a default argument fails when the function name is the same as the argument name?
f1 <- function(){
2
}
f2 <- function(f1 = f1()){
f1 * 2
}
f2()
#> Error in f2(): promise already under evaluation: recursive default argument reference or earlier problems?
I understand that the function can be made to work by (1) renaming the function f1 or (2) renaming the argument to f2 (see below), but I fail to understand why these fixes work while the case with aligned names fails. I also understand that many would consider this kind of naming to be bad form, but for the purposes of this question, I am interested in the processes underlying this error, not the associated deficit in style.
Fix 1
f <- function(){
2
}
f2 <- function(f1 = f()){
f1 * 2
}
f2()
#> [1] 4
Fix 2
f1 <- function(){
2
}
f2 <- function(.f1 = f1()){
.f1 * 2
}
f2()
#> [1] 4
Thank you in advance for your help.
Up front: this is because of how namespaces are searched through inheritance. One good reference is https://adv-r.hadley.nz/environments.html.
This is similar to how one argument is available to other arguments:
fun <- function(a=1, b=a+1, d=stop('huh?')) b+2
Sequence of evaluations:
b+2 triggers the evaluation of b, a formal argumentb=a+1 is evaluated, it includes a symbol, so ...a is in the same environment, so it evaluates a as 1 and passes it back up the chaind is never used, so it is never evaluatedYour issue is that inheritance of objects (frame to parent frame, etc, the search path for variables and dynamic scope) stops the moment it finds something. It looks in the current environment, the calling/parent frame, and so on until it reaches global() or a dead-end (not all paths end in globalenv()).
Your f1's function evaluation:
f1 = f1() finds f1 in the current environment, regardless of the fact that it's self-referential and still a "promise" under evaluation (not the same as promises's promise); since it is found, it stops searching and evaluates that one, which triggers a recursive evaluationAt no point is R going to say "well this f1 isn't working for me, let's continue looking further in the search path".
To be fair, a contrived example
f0 <- function(a=a) a
might suggest that in a self-referential argument should look further up the search path. And honestly that might be a plausible change to R's evaluation process, but it is not a minor change in behavior, and given the triviality of the "fix", I don't know that it'd be a high priority for the R Core devs (I don't speak for them).
The simplest (and perhaps obvious) way to not hit this problem is to rename the argument (or the function), as you demonstrated:
f2 <- function(.f1 = f1()) .f1 * 2
f2()
# [1] 4
I assume you're asking, though, because either you don't have control over both ends for some reason, or you really really want the argument to be named the same as another function.
An admittedly ugly way to do this could be
f2 <- function(f1 = parent.frame()$f1()){
f1 * 2
}
f2()
# [1] 4
Another option is to have "special internal processing" using missing or is.null:
f2 <- function(f1 = NULL) if (missing(f1)) f1()*2 else f1*2
f2()
# [1] 4
I should note the appearance of ambiguity here, where we're calling f1() in the presence of a null f1 non-function in the environment. @user2554330 mentions this as well, an anomaly in the whole "find something" process R goes though. I'm not sure why that differentiation doesn't occur in argument-evaluation, but it doesn't (at this time).
Along the same lines, we can keep the self-documenting formal argument of f1() in the formals with a simple check internally:
f2 <- function(f1 = f1()) {
if (missing(f1)) f1 <- getFunction("f1", where = parent.frame())()
f1 * 2
}
formals(f2)
# $f1
# f1()
This is nice for the documentation (which should not be ignored!) but is the same (computationally) as the f1=NULL example above: act on missing(f1).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With