r-quantities / units

Measurement units for R
https://r-quantities.github.io/units
173 stars 27 forks source link

Handling operations with units on custom objects #314

Closed dokato closed 2 years ago

dokato commented 2 years ago

Let's imagine that I have a class "my" and I want to trigger certain behaviour when it is added to an object that has units (i.e. from units package):

library(units)
my1 = structure(2, class="my")

Ops.my <- function(e1, e2=NULL) {
  ok <-
    switch(
      .Generic,
      `-` = ,
      `*` = ,
      `+` = ,
      '<=' = TRUE,
      FALSE
    )
  if (!ok) {
    stop(gettextf("%s not meaningful", sQuote(.Generic)))
  }
  get(.Generic)(as.integer(e1), as.integer(e2))
}

my1+set_units(5,nm)

Currently, it gives me the following warning:

Warning message:
Incompatible methods ("Ops.my", "Ops.units") for "+" 

But I actually want to handle "my" and "units" addition in a certain way*, how do I do it?

(*let's say class my performs addition only when unit is cm it returns 0 otherwise.)

Enchufa2 commented 2 years ago

This is a limitation of S3. Ops and a few more generics exceptionally dispatch on both arguments. This is required because, otherwise, x + y would behave differently from y + x. So if the two objects have different types, and both have Ops defined, there's no way for R to reliably pick one, thus the warning.

Maybe the vctrs framework allows you to do this? (Pinging @lionel-). Otherwise, you probably need to go full S4 here.

lionel- commented 2 years ago

We have an experimental mechanism along these lines (see the vec_arith() generic) but both the LHS and RHS must inherit from vctrs_vctr AFAICT, because of the dispatch limitations you mentioned. I'm not very familiar with this part of vctrs, maybe @davisvaughan knows more.

DavisVaughan commented 2 years ago

Yea this is an S3 limitation. The only way for users to be able to do <my> + <units> right now is to use S4 because both units and my define different Ops methods.

You can register double dispatch methods for vec_arith() (although it is a little clunky) and you can do vctrs::vec_arith("+", <my>, <units>), which would work as you'd expect. But that obviously isn't a pretty user interface.

Enchufa2 commented 2 years ago

And I don't know what's the use case here, but if your object intrinsically has units, then you could reuse units objects by defining a c("my", "units") class. Then you have full control, and could manage special cases in a certain way or otherwise rely on units by calling NextMethod.