Draft

A new package for conditions

R
Published

February 23, 2025

{cnd} is now on CRAN, and I’m pretty excited about this one.

In {fuj}, I included new_condition() which I used throughout {fuj}, and {mark}. Now, after a little more experience using custom condition classes, I’ve taken a step back and redesigned the implementation to really super charge usage.

Load up {cnd} and let’s get started.

Code
library(cnd)

There are two big functions here that will get highlighted:

condition()
and
cnd()

condition() creates a new cnd::condition_generator object, which is basically a fancy function. These hold additional metadata, which we’ll review in a bit, but know simply that we have to create a generator before creating a condition.

In the simpliest example, we just need to define a class name. We create the generator, and return it. In the print(), we can see the class and the type (“error”) of our condition.

Code
cond_my_new_condition <- condition("my_new_condition", type = "error")
cond_my_new_condition
#> cnd::condition_generator
#> my_new_condition/error

When we call the generator, it returns the new condition. A default message is applied.

Code
cond_my_new_condition()
#> my_new_condition/error
#> (my_new_condition/cnd::condition/error/condition)
#> there was an error

That condition can be passed into stop(), to singal the condition and print the message.

Code
stop(cond_my_new_condition())
#> Error in cond_my_new_condition(): <my_new_condition>
#> there was an error

Now, let’s look at a more complicated example. We’re going to define a new condition generator, and call it within a function.

Code
cond_numeric_error <- condition(
  "numeric_error",
  message = "parameters must be numeric",
  type = "error",
  package = "example"
)

cond_numeric_error
#> cnd::condition_generator
#> example:numeric_error/error

summation <- function(x, y) {
  if (!is.numeric(x) || !is.numeric(y)) {
      stop(cond_numeric_error())
  }
  x + y
}

summation(1, 2)
#> [1] 3
summation(1, "2")
#> Error in summation(): <example:numeric_error>
#> parameters must be numeric

With a small adjustment, we can also turn this into a “warning” instead, and throw some additional information. Because we’re using a function, the print() for the cnd::condition_generator object gains a new generator output, which shows information about this function.

Code
cond_numeric_warning <- condition(
  "numeric_warning",
  message = function(x, y) {
    x <- typeof(x)
    y <- typeof(y)
    types <- setdiff(c(x, y), c("double", "integer"))
    c(
      paste("parameters must be numeric, not:", toString(types)),
      "A coercion attempt will be made"
    )
  },
  type = "warning",
  package = "example"
)

cond_numeric_warning
#> cnd::condition_generator
#> example:numeric_warning/warning 
#> 
#> generator
#>   $ x : <symbol> 
#>   $ y : <symbol>

summation <- function(x, y) {
  if (!is.numeric(x) || !is.numeric(y)) {
    print(sys.call())
    warning(cond_numeric_warning(x, y))
    x <- as.numeric(x)
    y <- as.numeric(y)
  }
  x + y
}

summation(1, 2)
#> [1] 3
summation(1, "2")
#> summation(1, "2")
#> Warning in summation(): <example:numeric_warning>
#> parameters must be numeric, not: character
#> A coercion attempt will be made
#> [1] 3
summation(list(1), "2")
#> summation(list(1), "2")
#> Warning in summation(): <example:numeric_warning>
#> parameters must be numeric, not: list, character
#> A coercion attempt will be made
#> [1] 3

You can assign conditions to function, which stores them in the "conditions" attribute. This also updates the class of the object, which allows us to see which conditions has been assigned to the function.

Code
conditions(summation) <- cond_numeric_warning
summation
#> function (x, y) 
#> {
#>     if (!is.numeric(x) || !is.numeric(y)) {
#>         print(sys.call())
#>         warning(cond_numeric_warning(x, y))
#>         x <- as.numeric(x)
#>         y <- as.numeric(y)
#>     }
#>     x + y
#> }
#> <bytecode: 0x567c75219308>
#> 
#> condition(s)
#> example:numeric_warning/warning

Conditions do not need to be exported to be retrieved. When you see the name, you can use that as a character string within cond() to retrieve the object.

Code
cond("example:numeric_warning")
#> cnd::condition_generator
#> example:numeric_warning/warning 
#> 
#> generator
#>   $ x : <symbol> 
#>   $ y : <symbol>

{cnd} will try to use the {cli} package under the hood. In places where the condition names are printed, you should be able to click on them, too, which will execute cond() and retrieve the condition.

For more general searches, use conditions() and define some options. Below we will grab all warning conditions from {cnd}.

Code
conditions("cnd", type = "warning")
#> [[1]]
#> cnd::condition_generator
#> cnd:cnd_document_conditions/warning 
#> 
#> exports
#>   cnd::cnd_document()
#> 
#> [[2]]
#> cnd::condition_generator
#> cnd:condition_overwrite/warning 
#> 
#> generator
#>   $ old : <symbol> 
#>   $ new : <symbol> 
#> 
#> exports
#>   cnd::condition()
#> 
#> [[3]]
#> cnd::condition_generator
#> cnd:conditions_dots/warning 
#> 
#> help
#> The `...` parameter in [conditions()] is meant for convenience.  Only a single argument is allowed.  Other parameters must be named  explicitly.  For example:  ```r # Instead of this conditions("class", "package") # "package" is ignored with a warning  # Do this conditions(class = "class", package = "package") ``` 
#> 
#> exports
#>   cnd::conditions()
#> 
#> [[4]]
#> cnd::condition_generator
#> cnd:no_package_exports/warning 
#> 
#> help
#> The `exports` parameter requires a `package` 
#> 
#> exports
#>   cnd::condition()

One of the most exciting features about {cnd} is the package documentation. It’s now very easy to define new conditions, which signal warnings or errors, and provide users with more immediate knowledge.

With our summation() function, we can generate roxygen friendly documentation

Code
cat(cnd_section(summation))
#> 
#> Conditions are generated through the [`{cnd}`][cnd::cnd-package] package.
#> The following conditions are associated with this function:
#> 
#> \describe{
#>   
#>   \item{[`example:numeric_warning/warning`][example-cnd-conditions]}{
#>     
#>   }
#> 
#> }
#> 
#> For more conditions, see: [example-cnd-conditions]

For more information, see https://jmbarbone.github.io/cnd and https://github.com/jmbarbone/cnd.