Search code examples
rnon-standard-evaluation

Making informative `stopifnot()` errors using NSE in R


I want to make informative stopifnot() errors.

I've read: http://r-pkgs.had.co.nz/tests.html (the section at the end on using NSE to make informative test error print out for the example seems relevant) and http://adv-r.had.co.nz/Computing-on-the-language.html but I cant get this to print an informative error in concise code:

e <- new.env()
e$label <- c(1,2,3)
check_in_y <- function(x, z, e) {
  stopifnot(eval(bquote(.(x) %in% e[[.(z)]])))
}

check_in_y(5,"label", e)

The output gives this (not so informative)

Error: eval(bquote(.(x) %in% e[[.(z)]])) is not TRUE

I want the error to be more informative, saying this:

Error: 5 %in% e[["label"]] is not TRUE

How can I get this to work? Or what's the best approach to achieve what I want

I know I could write an if condition not true then print my own error as an alternative, but the extra code is a hassle. I'd like to understand how to get NSE to get this to work.

Edit: My motivation from this approach came from reading hadley's comments (at http://r-pkgs.had.co.nz/tests.html):

However, if the expectation fails this doesn’t give very informative output:

expect_floor_equal("year", "2008-01-01 00:00:00")
## Error: floor_date(base, unit) not equal to as.POSIXct(time, tz = "UTC")
## Mean absolute difference: 31622400

Instead you can use a little non-standard evaluation to produce something more informative. The key is to use bquote() and eval(). In the bquote() call below, note the use of .(x) - the contents of () will be inserted into the call.

expect_floor_equal <- function(unit, time) {
  as_time <- function(x) as.POSIXct(x, tz = "UTC")
  eval(bquote(expect_equal(floor_date(base, .(unit)), as_time(.(time)))))
}
expect_floor_equal("year", "2008-01-01 00:00:00")
## Error: floor_date(base, "year") not equal to as_time("2008-01-01 00:00:00")

Solution

  • stopifnot is just a convenience function for

    if(!all(condition)) stop(standard message)
    

    For custom messages, just write the code. You can replace the stopifnot call with two lines:

    check_in_y <- function(x, z, e) {
        b <- bquote(.(x) %in% e[[.(z)]])
        if(!eval(b)) stop(deparse(b), " is not TRUE", call. = FALSE)
    }
    
    check_in_y(5, "label", e)
    # Error: 5 %in% e[["label"]] is not TRUE