简体   繁体   中英

Nesting glue function in custom function

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

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

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

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

Since you're already using glue you could use another glue::glue in test_fc to accomplish the pass-through, such as:

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

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

which yields

> test_fc('arctic blast')
[INFO 2022-12-21 15:56:18] arctic blast is here
>

It looks like the issue is that you are trying to use the forecast argument in the glue::glue() function without actually passing it in as an argument. The... in the log_fc() function allows you to pass in any number of additional arguments, but you need to use them within the function by naming them.

One way to fix this would be to modify the log_fc() function to accept the forecast argument and pass it into the glue::glue() function:

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

Then you can call the log_fc() function with the forecast argument, like this:

test_fc <- function(forecast) {
  log_fc(type = "INFO", forecast)
  #print(forecast)
}

This should allow the forecast argument to be passed through to the inner log_fc() function and used in the glue::glue() function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM