1

I've created a new class to print percentages with vctrs, like explained in https://vctrs.r-lib.org/articles/s3-vector.html . It works well when I source the .R file. But when I build the package with devtools, basic operations made possible with vec_arith don't work anymore :

pct(0.5, 1L) + pct(0.25, 2L)
#Error: <pct0> + <pct0> is not permitted

However, similar cases are working well : conversions are good ; vec_math is also OK, it's possible to do sum(pct(0.5, 1L) + pct(0.25, 2L))

I've made a reproducible example, which contains the code necessary to load a small package with the percent class using devtools 2.3.2 and vctrs 0.3.6.

  1. We create a new package named percenterror with devtools :
create_package("D:\\Statistiques\\Packages\\percenterror")

On the console inside the RStudio project of this package, we declare dependencies and create a new .R file :

library(devtools)
use_package("vctrs",     min_version = "0.3.6")
use_r("percent_class")
  1. Let's copy the following code in the .R file that opens, with roxygen tags to generate the NAMESPACE :
#'  Import vctrs in NAMESPACE
#' @import vctrs
#' @keywords internal
#' @name vctrs
NULL

#' Create a vector of class pct using vctrs
#' @param x A double vector.
#' @param digits The number of digits to print. It can then be changed
#' with \code{\link{set_digits}}.
#'
#' @return A numeric vector of class pct.
#' @export
pct <- function(x = double(), digits = 0L) {
  x <- vctrs::vec_cast(x, double()) #take anything coercible as a double
  digits <- vctrs::vec_recycle(vctrs::vec_cast(digits, integer()), 1L)
  new_pct(x, digits)
}

#' @describeIn pct A constructor for class pct.
#' @export
new_pct <- function(x = double(), digits = 0L) {
  vctrs::vec_assert(x, double()) #check type or size
  vctrs::vec_assert(digits, ptype = integer(), size = 1)
  vctrs::new_vctr(x, digits = digits, class = "pct", inherit_base_type = TRUE) #"vctrs_pct"
}

#' Get and set number of digits of vectors with class pct
#' @param x A vector of class \code{\link{pct}} or \code{\link{decimal}}
#' @return \code{\link{get_digits}} : an integer vector with the number of digits.
#' @export
get_digits <- function(x) as.integer(attr(x, "digits"))
#' @rdname get_digits
#' @param value The number of digits to print, as an integer.
#' @return \code{\link{set_digits}} : a vector with the right number of digits.
set_digits <- function(x, value) `attr<-`(x, "digits", as.integer(value))

#' A vec_arith method to allow basic operations for pct
#' @param op Arithmetic operation to do.
#' @param x Pct object.
#' @param y Second object.
#'
#' @export
vec_arith.pct <- function(op, x, y, ...) {
  UseMethod("vec_arith.pct", y)
}

#' @export
vec_arith.pct.default <- function(op, x, y, ...) {
  vctrs::stop_incompatible_op(op, x, y)
}


#' @export
vec_arith.pct.pct <- function(op, x, y, ...) {
  new_pct(vctrs::vec_arith_base(op, x, y),
          digits = max(get_digits(x), get_digits(y)))
}

#' @export
vec_arith.pct.numeric <- function(op, x, y, ...) {
  new_pct(vctrs::vec_arith_base(op, x, y),
          digits = max(get_digits(x), get_digits(y)))
}

#' @export
vec_arith.numeric.pct <- function(op, x, y, ...) {
  new_pct(vctrs::vec_arith_base(op, x, y),
          digits = max(get_digits(x), get_digits(y)))
}

If we do library(vctrs) and source this document, pct(0.5) + pct(0.25) works fine.

  1. But then, back in the console, we create documentation and load the package :
document()
load_all()

Here, additions don't work anymore :

pct(0.5, 1L) + pct(0.25, 2L)
#Error: <pct0> + <pct0> is not permitted

However the method is found :

sloop::s3_get_method("vec_arith.pct.pct")
# function(op, x, y, ...) {
#   new_pct(vctrs::vec_arith_base(op, x, y),
#           digits = max(get_digits(x), get_digits(y)))
# }
#<environment: namespace:percenterror>

The method seems linked to the right generic :

sloop::s3_methods_generic("vec_arith.pct")
# # A tibble: 3 x 4
# generic         class   visible source
# <chr>           <chr>   <lgl>   <chr>
# 1 vec_arith.pct default TRUE    percenterror
# 2 vec_arith.pct numeric TRUE    percenterror
# 3 vec_arith.pct pct     TRUE    percenterror

vec_arith.numeric method works with pct :

1 + pct(0.5)
# <pct0[1]>
#   [1] 150%

But the opposite is not true, vec_arith_pct method does'nt work with numeric :

pct(0.5) + 1
#Error: <pct0> + <double> is not permitted

When we run the trace of the error with rlang::last_trace(), we find that the pct + pct operation does'nt in fact go to the right method, and does'nt even care about the functions defined above, because vec_arith.default is used in place of vec_arith.pct :

# <error/vctrs_error_incompatible_op>
#   <pct0> + <pct0> is not permitted
# Backtrace:
#   x
# 1. \-vctrs:::`+.vctrs_vctr`(pct(0.5, 0L), pct(0.25000001))
# 2.   +-vctrs::vec_arith("+", e1, e2)
# 3.   \-vctrs:::vec_arith.default("+", e1, e2)
# 4.     \-vctrs::stop_incompatible_op(op, x, y)
# 5.       \-vctrs:::stop_incompatible(...)
# 6.         \-vctrs:::stop_vctrs(...)

What went wrong, and what to do to make it works ? I've tried with ou without vctrs:: calls, importing or not importing almost everything in NAMESPACE, but can't manage to find what happens.

Thanks

  • I think you need to post something reproducible if you want help. This isn't going to be easy: packages are usually too big to post. Maybe if you put it on Github, and give people instructions for installing it and reproducing the error? Probably not acceptable to SO (since questions should be self contained), but maybe you'll get an answer anyway. – user2554330 Dec 28 '20 at 14:50
  • It appears from the error message that the dispatch process got lost at `vec_arith.default` and never got anywhere near those functions defined above. I think you may need to examine the code in the `vctrs` package to find out how to get a new class argument dispatched correctly. – IRTFM Dec 29 '20 at 17:13

1 Answers1

1

You need to add roxygen2 @method tags to export your vec_arith. functions as methods:

#' A vec_arith method to allow basic operations for pct
#' @param op Arithmetic operation to do.
#' @param x Pct object.
#' @param y Second object.
#'
#' @method vec_arith pct
#' @export
vec_arith.pct <- function(op, x, y, ...) {
  UseMethod("vec_arith.pct", y)
}

#' @method vec_arith.pct default
#' @export
vec_arith.pct.default <- function(op, x, y, ...) {
  vctrs::stop_incompatible_op(op, x, y)
}

#' @method vec_arith.pct pct
#' @export
vec_arith.pct.pct <- function(op, x, y, ...) {
  new_pct(vctrs::vec_arith_base(op, x, y),
          digits = max(get_digits(x), get_digits(y)))
}

#' @method vec_arith.pct numeric
#' @export
vec_arith.pct.numeric <- function(op, x, y, ...) {
  new_pct(vctrs::vec_arith_base(op, x, y),
          digits = max(get_digits(x), get_digits(y)))
}

#' @method vec_arith.numeric pct
#' @export
vec_arith.numeric.pct <- function(op, x, y, ...) {
  new_pct(vctrs::vec_arith_base(op, x, y),
          digits = max(get_digits(x), get_digits(y)))
}

This requirement has been removed from vctrs' double dispatch mechanism for most generics, but for the time being it's still required for vec_arith(): https://github.com/r-lib/vctrs/issues/1287

Joe Roe
  • 626
  • 3
  • 12
  • 1
    Thank you very much Joe Roe it works perfectly (I have tried before with ```@method```, but not consistently enougth to put it in the generic) ! Just a detail : with ```vec_arith pct default``` , I have a "Warning: [D:\... ] @method can have at most 2 words". I replaced with ```vec_arith.pct default``` and it worked. – Brice Nocenti Feb 04 '21 at 09:59
  • Oops, my mistake. The syntax is `@method ` so yes you need e.g. `vec_arith.pct numeric` to double dispatch the `vec_arith.pct()` method with a numeric. – Joe Roe Feb 04 '21 at 13:14