Home > front end >  Nesting glue function in custom function
Nesting glue function in custom function

Time:12-19

I want to create a custom log function, that would get used in other functions. I am having issues with the custom function where arguments don't seem to flow through to the inner log function. My custom log function is inspired by the logger package but I am planning to expand this usage a bit further (so logger doesn't quite meet my needs)

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  
  print(
    glue::glue("[{type} {Sys.time()}] ", ...)
  )
  
}

Next I am planning to use log_fc in various other custom functions, one example:

test_fc <- function(forecast) {

  log_fc(type = "INFO", "{forecast} is here")
  
  #print(forecast)
}

If I test this, I get the following error:

> test_fc(forecast = "d")
 Error in eval(parse(text = text, keep.source = FALSE), envir) : 
object 'forecast' not found

I am not sure why argument forecast is not being picked up by the inner test_fc function. TIA

CodePudding user response:

You could use the .envir argument:

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  env <- new.env(parent=parent.frame())
  assign("type",type,env)
  print(
    glue::glue("[{type} {Sys.time()}] ", ...,.envir = env)
  )
}


  
test_fc <- function(forecast) {
    
    log_fc(type = "INFO", "{forecast} is here")
    
}

  
test_fc("My forecast")
#> [INFO 2022-12-18 12:44:11] My forecast is here

CodePudding user response:

There are two things going on.

First, the name forecast is never passed to log_fc. The paste solution never needs the name, it just needs the value, so it still works. You'd need something like

log_fc(type = "INFO", "{forecast} is here", forecast = forecast)

to get the name into log_fc.

The second issue is more complicated. It's a design decision in many tidyverse functions. They want to be able to have code like f(x = 3, y = x 1) where the x in the second argument gets the value that was bound to it in the first argument.

Standard R evaluation rules would not do that; they would look for x in the environment where f was called, so f(y = x 1, x = 3) would bind the same values in the function as putting the arguments in the other order.

The tidyverse implementation of this non-standard evaluation messes up R's internal handling of .... The workaround (described here: https://github.com/tidyverse/glue/issues/231) is to tell glue() to evaluate the arguments in a particular location. You need to change your log function to fix this.

One possible change is shown below. I think @Waldi's change is actually better, but I'll leave this one to show a different approach.

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  # Get all the arguments from ...
  args <- list(...)
  
  # The unnamed ones are messages, the named ones are substitutions
  named <- which(names(args) != "")
  
  # Put the named ones in their own environment
  e <- list2env(args[named])
  
  # Evaluate the substitutions in the new env
  print(
    glue::glue("[{type} {Sys.time()}] ", ..., .envir = e)
  )
}

test_fc <- function(forecast) {
  
  log_fc(type = "INFO", "{forecast} is here", forecast = forecast)

  }


test_fc(forecast = "d")
#> [INFO 2022-12-18 06:25:29] d is here

Created on 2022-12-18 with reprex v2.0.2

CodePudding user response:

The reason for this is that when your test_fc function connects to the log_fc function, the forecats variable wouldn't be able to be found, because it's not a global function; thus, you can't access it from the other function.

The way to fix this is by defining a global variable:

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  
  print(
    glue::glue("[{type} {Sys.time()}] ", ...)
  )
  
}

test_fc <- function(forecast) {

  forecast <<- forecast
  log_fc(type = "INFO", "{forecast} is here")
 
}

print(test_fc(forecast = "d"))

Output:

d is here
  • Related