2017-09-22 19:04 GMT+02:00 Michael Lawrence <lawrence.michael at gene.com>:> The %*% function is a primitive. As it says in the documentation under > ?Methods_Details > > Methods may be defined for most primitives, and corresponding > metadata objects will be created to store them. Calls to the > primitive still go directly to the C code, which will sometimes > check for applicable methods. The definition of ?sometimes? is > that methods must have been detected for the function in some > package loaded in the session and ?isS4(x)? is ?TRUE? for the > first argument (or for the second argument, in the case of binary > operators). > > But: >> isS4(x) > [1] FALSE > > I think this behavior is in the interest of performance. It avoids > adding S4 dispatch overhead to e.g. matrix objects.I see, thanks for the explanation.> In general, it's best to define an S4 class when using S4 dispatch, > but it sounds like you're stuck using some legacy S3 objects. In that > case, one would normally define an S3 method for `%*%()` that > delegates to a custom non-primitive generic, perhaps "matmult" in this > case. But since %*% is not an S3 generic, that's not an option. > > It would help to hear more about the context of the problem.This is a problem that Edzer Pebesma and I are facing in our packages units and errors respectively (you can find both on CRAN). They are designed in a similar way: units or errors are attached to numeric vectors as an attribute of an S3 class, and Ops, Math and Summary are redefined to cope with such units/errors. Then Edzer found that the %*% operator silently drops the attributes, so we were trying, without success, to set a method for our respective S3 classes to at least show a warning stating that the units/errors are being dropped. Ours are perfect use cases for S3 classes, and it would be a pitty if we have to switch everything to S4 just to show a warning. Clearly overkilling. Isn't there another way? I?aki
On Fri, Sep 22, 2017 at 10:28 AM, I?aki ?car <i.ucar86 at gmail.com> wrote:> 2017-09-22 19:04 GMT+02:00 Michael Lawrence <lawrence.michael at gene.com>: >> The %*% function is a primitive. As it says in the documentation under >> ?Methods_Details >> >> Methods may be defined for most primitives, and corresponding >> metadata objects will be created to store them. Calls to the >> primitive still go directly to the C code, which will sometimes >> check for applicable methods. The definition of ?sometimes? is >> that methods must have been detected for the function in some >> package loaded in the session and ?isS4(x)? is ?TRUE? for the >> first argument (or for the second argument, in the case of binary >> operators). >> >> But: >>> isS4(x) >> [1] FALSE >> >> I think this behavior is in the interest of performance. It avoids >> adding S4 dispatch overhead to e.g. matrix objects. > > I see, thanks for the explanation. > >> In general, it's best to define an S4 class when using S4 dispatch, >> but it sounds like you're stuck using some legacy S3 objects. In that >> case, one would normally define an S3 method for `%*%()` that >> delegates to a custom non-primitive generic, perhaps "matmult" in this >> case. But since %*% is not an S3 generic, that's not an option. >> >> It would help to hear more about the context of the problem. > > This is a problem that Edzer Pebesma and I are facing in our packages > units and errors respectively (you can find both on CRAN). They are > designed in a similar way: units or errors are attached to numeric > vectors as an attribute of an S3 class, and Ops, Math and Summary are > redefined to cope with such units/errors. > > Then Edzer found that the %*% operator silently drops the attributes, > so we were trying, without success, to set a method for our respective > S3 classes to at least show a warning stating that the units/errors > are being dropped. > > Ours are perfect use cases for S3 classes, and it would be a pitty if > we have to switch everything to S4 just to show a warning. Clearly > overkilling. Isn't there another way? >There is formally no such thing as an S3 class. Just S3 objects with a class attribute. You could extend a base class with an S4 class, like setClass("IntegerWithUnits", slots=c(units="Units"), contains="integer"). Sure, that's a disruptive change, but it would be in the right direction. Extending base classes is always a risky proposition, as you've discovered. Ideally you would override every transformation in order to adjust or carry over the representation accordingly. The problem is that there's a huge amount of transformations available for base classes, mostly not encapsulated by generics. Other possibilities include: - Convincing someone to make %*% an internal S3 generic - Promoting %*% to an R-level S3 generic, which would only work with code that sees your namespace Hopefully others have better ideas, Michael> I?aki >
> There is formally no such thing as an S3 class. Just S3 objects with a > class attribute. You could extend a base class with an S4 class, like > setClass("IntegerWithUnits", slots=c(units="Units"), > contains="integer"). Sure, that's a disruptive change, but it would be > in the right direction. > > Extending base classes is always a risky proposition, as you've > discovered. Ideally you would override every transformation in order > to adjust or carry over the representation accordingly. The problem is > that there's a huge amount of transformations available for base > classes, mostly not encapsulated by generics. > > Other possibilities include: > - Convincing someone to make %*% an internal S3 generic > - Promoting %*% to an R-level S3 generic, which would only work with > code that sees your namespace > > Hopefully others have better ideas, > MichaelThanks for your kind help and suggestions, Michael. I think I'll take the last option as a workaround, at least until I evaluate the advantages and drawbacks of a complete redesign. I?aki