Lexical scoping / calling stack issue: R fails to recognize an argument's default value

71 views Asked by At

This is sort of the strangest thing I ever encountered in R.

Is it possible, that certain argument names (lazy in my case) are special/reserved and thus would lead to unexpected behavior when a calling stack is involved that spreads across functions of three different packages: optionr::setAnywhereOptions() calls nestr::setNested() calls reactr::setShinyReactive()?

Consider the following example/situation (also described in this GitHub issue)

Adapted from this unit test in package optionr

require("devtools")
devtools::install_github("Rappster/conditionr")
devtools::install_github("Rappster/typr")
devtools::install_github("Rappster/nestr")
devtools::install_github("Rappster/reactr", ref = "bug-28")

devtools::install_github("Rappster/optionr")
require("optionr")

path <- file.path(tempdir(), "test")
create(path, description = getOption("devtools.desc"), check = FALSE,
  rstudio = TRUE)
setwd(path)

container <- initializeOptionContainer(overwrite = TRUE)  
setAnywhereOption(id = "x_1", value = TRUE, reactive = TRUE)
getAnywhereOption(id = "x_1")
setAnywhereOption(id = "x_2", 
  value = reactr::reactiveExpression(
    !getAnywhereOption(id = "x_1")
  ), 
  reactive = TRUE
)

getAnywhereOption(id = "x_1")
getAnywhereOption(id = "x_2")

Note the status messages that I included for debugging:

DEBUG/push/before
[1] FALSE
DEBUG/lazy/before
Error in print(lazy) : argument is missing, with no default
DEBUG/is_lazy/before
[1] FALSE
DEBUG/lazy/after
[1] FALSE

For some reason, for this very calling stack structure, function reactr::setShinyReactive() does not seem to recognize the default value for lazy anymore while others (e.g. that of push) seem to be recognized just fine.

To be more precise, without my workaround (see below), R would complain when reaching this code section. However, as you can see, the default value is defined and it also works just fine if the function is called stand-alone or from within nestr::setNested().

Workaround

When I change the argument name from lazy to is_lazy (or just include is_lazy as well and then run lazy <- is_lazy in the body), everything works just fine (see debug output above).

See DEBUG section in reactr::setShinyReactive()

Wildly guessing what's going on here

The first thing that comes to my mind is that this must have something to do with the lexical scoping mechanism as the behavior does not appear for "simple function call stacks".

The second guess is that maybe the S4 mechanism also causes some trouble here. Maybe also related to this issue?

1

There are 1 answers

0
Rappster On

Ok, I have to admit: that was a really stupid mistake. I unintentionally had a trailing , in the call to nestr::setNested() inside optionr::setAnywhereOption()

Here's a much simpler illustration:

Definitions

setGeneric(
  name = "setAnywhereOption",
  signature = "id",
  def = function(id, ...) standardGeneric("setAnywhereOption")      
)
setMethod(
  f = "setAnywhereOption", 
  signature = signature(id = "character"), 
#  definition = function(id, ...) setNested(id = id)
  ## --> works
#  definition = function(id, ...) setNested(id = id, ...)
  ## --> works
  definition = function(id, ...) setNested(id = id,)
  ## --> this leads to things get messed up with argument's default values
  ## --> so the trailing `,` was causing the problem!
)
setGeneric(
  name = "setNested",
  signature = "id",
  def = function(id, ...) standardGeneric("setNested")       
)
setMethod(
  f = "setNested", 
  signature = signature(id = "character"), 
  definition = function(id, ...) {

  if (FALSE) {  
    ## Omitted 
  } else {
    setShinyReactive(id = basename(id), ...)
  }

})
setShinyReactive <- function(
  id,
  lazy = FALSE,
  is_lazy = FALSE,
  push = FALSE,
  typed = FALSE,
  strict_set = c(0, 1, 2),
  ...
  ) {

  ###########
  ## DEBUG ##    
  ###########
  message("DEBUG/setShinyReactive/threedots")
  print(list(...))
  message("DEBUG/setShinyReactive/push")        
  print(push)    
  message("DEBUG/setShinyReactive/lazy")    
  try(print(lazy))
  ## --> strangely, R does not seem to like the name `lazy` 
  message("DEBUG/setShinyReactive/is_lazy")    
  print(is_lazy)
  ## --> this works
  lazy <- is_lazy
  message("DEBUG/setShinyReactive/lazy")    
  print(lazy)

  TRUE

}

Apply

setAnywhereOption(id = "test")
# DEBUG/setShinyReactive/threedots
# list()
# DEBUG/setShinyReactive/push
# [1] FALSE
# DEBUG/setShinyReactive/lazy
# Error in print(lazy) : argument is missing, with no default
# DEBUG/setShinyReactive/is_lazy
# [1] FALSE
# DEBUG/setShinyReactive/lazy
# [1] FALSE
# [1] TRUE
setAnywhereOption(id = "test", push = TRUE)
setAnywhereOption(id = "test", lazy = TRUE)

Solution

Removing the trailing , in the method definition of setAnywhereOption():

setMethod(
  f = "setAnywhereOption", 
  signature = signature(id = "character"), 
  definition = function(id, ...) setNested(id = id)
  ## --> works
#  definition = function(id, ...) setNested(id = id,)
  ## --> this leads to things get messed up with argument's default values
  ## --> so the trailing `,` was causing the problem!
)

setAnywhereOption(id = "test")
# DEBUG/setShinyReactive/threedots
# list()
# DEBUG/setShinyReactive/push
# [1] FALSE
# DEBUG/setShinyReactive/lazy
# [1] FALSE
# DEBUG/setShinyReactive/is_lazy
# [1] FALSE
# DEBUG/setShinyReactive/lazy
# [1] FALSE
# [1] TRUE

Now it works just fine