Should a vctrs constructor function always return a zero-length vector?

The vctrs S3 vectors vignette advises says that user-friendly constructors should return a zero-length vector when called with no arguments, as in the percent() example:

library("vctrs")
new_percent <- function(x = double()) {
  vec_assert(x, double())
  new_vctr(x, class = "vctrs_percent")
}

percent <- function(x = double()) {
  x <- vec_cast(x, double())
  new_percent(x)
}

percent()
#> <vctrs_percent[0]>

The explanation is that this makes it easier to use as a prototype, as in vec_cast(x, percent()).

But later, the rational() example does not follow this advice:

library("vctrs")
library("zeallot")
new_rational <- function(n = integer(), d = integer()) {
  vec_assert(n, ptype = integer())
  vec_assert(d, ptype = integer())
  
  new_rcrd(list(n = n, d = d), class = "vctrs_rational")
}

rational <- function(n, d) {
  c(n, d) %<-% vec_cast_common(n, d, .to = integer())
  c(n, d) %<-% vec_recycle_common(n, d)
  
  new_rational(n, d)
}

rational()
#> Error in vec_cast_common(n, d, .to = integer()): argument "n" is missing, with no default

So presumably to invoke the rational class as a prototype, you would use vec_cast(x, new_rational()).

I'm struggling to understand which pattern to use when implementing my own vctrs-based classes. On the one hand, it is useful that percent() returns something that works as a prototype, because to allow the user to do the same with rational you'd have to export the internal constructor new_rational(), which doesn't seem ideal. On the other hand, it is awkward to maintain this behaviour with more complex constructors, e.g. with defaults and required arguments, and ends up with a signature which is not as easy for the user to understand.

Is there any advice on if/when it's acceptable for constructors not to return a zero-length vector? Does it matter if the class is a vctr or rcrd? And if you write one that doesn't, should the low-level constructor new_x() be exported and use as a prototype?

I think that's a bug in that class, rational() should have default arguments. I've opened an issue so we don't forget to update the doc, thanks! https://github.com/r-lib/vctrs/issues/1321

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.