There are languages which allow to have a trailing comma, for example after the last item in a list: https://www.jetbrains.com/help/rider/Trailing_Commas_Style.html. In R however, this is most of the time not the case:
lst <- list(
a = 1, # notice the faulty comma here
)
# Error in list(a = 1, ) : argument 2 is empty
Things get a bit more complicated in when calling functions with ellipsis arguments:
fn_with_ellipsis <- function(x, ...)
{
message("x: ", x)
}
# this triggers NO error
fn_with_ellipsis(x = 42, )
# x: 42
What triggered this blog entry is the following example:
# this is ok, gives the error we expect
rlang::abort(
message = "leaving this world",
class = "example_error_class"
)
# Error:
# ! leaving this world
# Run `rlang::last_error()` to see where the error occurred.
Recently, we switched rlang version 1.0.1. As a result, the following code triggered an error:
rlang::abort(
message = "leaving this world",
class = "example_error_class", # notice the faulty comma here
)
# Error in `list2()`:
# ! Argument 3 is empty
# Run `rlang::last_error()` to see where the error occurred.
So I had to do a hot fix only for one comma...
The interesting part is: how can I actually steer the response to trailing commas in the context of ellipsis. The rlang::dots_list function helps with this:
fn_with_ellipsis <- function(x, ...)
{
message("x: ", x)
rlang::dots_list(..., .ignore_empty = "trailing")
}
fn_with_ellipsis(x = 42, )
# x: 42
# named list()
fn_with_ellipsis <- function(x, ...)
{
message("x: ", x)
rlang::dots_list(..., .ignore_empty = "none")
}
fn_with_ellipsis(x = 42, )
# x: 42
# Error in `rlang::dots_list()`:
# ! Argument 1 is empty
# Run `rlang::last_error()` to see where the error occurred.
I often have trailing commas issues when writing R6 classes.
ExampleClass <- R6::R6Class(
public = list(
initialize = function(){}, # notice the faulty comma here
),
private = list()
)
# Error in list(initialize = function() { : argument 2 is empty
The problem might seem obvious here, but in practice the classes contain a lot of code and knowing what this error message means narrows down the search.
For completeness, here is another error that I met some days ago, while refactoring some code into R6 classes:
ExampleClass <- R6::R6Class(
public = list(
initialize = function(){
self$public_member = 42
}
),
private = list()
)
obj <- ExampleClass$new()
# Error in self$public_member = 42 :
# cannot add bindings to a locked environment
Reason: forgotten declaration of public member (see below). Note: the same error is issued in case of private members.
ExampleClass <- R6::R6Class(
public = list(
public_member = NULL, # this was missing above
initialize = function(){
self$public_member = 42
}
),
private = list()
)
obj <- ExampleClass$new()
# no error
ExampleClass <- R6::R6Class(
public = list(
public_member = NULL, # this was missing above
initialize = function(){
self$public_member = 42
private$private_member2 = "foo"
}
),
private = list()
)
obj <- ExampleClass$new()
# Error in private$private_member2 = "foo" : object 'private' not found
ExampleClass <- R6::R6Class(
public = list(
public_member = NULL, # this was missing above
initialize = function(){
self$public_member = 42
private$private_member2 = "foo"
}
),
private = list(
private_member1 = NULL
)
)
obj <- ExampleClass$new()
# Error in private$private_member2 = "foo" :
# cannot add bindings to a locked environment
ExampleClass <- R6::R6Class(
public = list(
public_member = NULL, # this was missing above
initialize = function(){
self$public_member = 42
private$private_member2 = "foo"
}
),
private = list(
private_member1 = NULL,
private_member2 = NULL # this was missing above
)
)
obj <- ExampleClass$new()
# ok, no error
Just in case you don't need as much sleeping time as me, and if you feel curious enough, here is a link to A reading club for software developers which I highly recommend.
Skimming through the articles posted there, here some highlights:
- An interactive explanation of how GPS works
- What does it mean to listen to a port
- How postgres stores rows
- How UTF-8 Works
- Python bytecode explained
- How JPEG compression works
- Learning containers from the bottom up
- Bash functions are better than I thought
- A 16 year history of the git init command
- A brief introduction to termios
- Array indices point between elements
Make a promise. Show up. Do the work. Repeat.