S4 method implementation for S3 class

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

S4 method implementation for S3 class

Iñaki Úcar
Hi all,

I'm trying to implement the matrix multiplication operator, which is
S4 generic, for an old-style S3 class. The following works as
expected:

x <- 1:10
class(x) <- "myClass"

setOldClass("myClass")
setGeneric("myMethod", function(x, y) standardGeneric("myMethod"))
setMethod("myMethod", c("myClass", "myClass"), function(x, y)
message("dispatched!"))

myMethod(x, x)
#> dispatched!

but I don't understand why the following won't:

setMethod("%*%", c("myClass", "myClass"), function(x, y) message("dispatched!"))

x %*% x
#>      [,1]
#> [1,]  385

Is this approach wrong?

Regards,
Iñaki

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 method implementation for S3 class

Michael Lawrence-3
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.

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.

Michael



On Fri, Sep 22, 2017 at 5:44 AM, Iñaki Úcar <[hidden email]> wrote:

> Hi all,
>
> I'm trying to implement the matrix multiplication operator, which is
> S4 generic, for an old-style S3 class. The following works as
> expected:
>
> x <- 1:10
> class(x) <- "myClass"
>
> setOldClass("myClass")
> setGeneric("myMethod", function(x, y) standardGeneric("myMethod"))
> setMethod("myMethod", c("myClass", "myClass"), function(x, y)
> message("dispatched!"))
>
> myMethod(x, x)
> #> dispatched!
>
> but I don't understand why the following won't:
>
> setMethod("%*%", c("myClass", "myClass"), function(x, y) message("dispatched!"))
>
> x %*% x
> #>      [,1]
> #> [1,]  385
>
> Is this approach wrong?
>
> Regards,
> Iñaki
>
> ______________________________________________
> [hidden email] mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 method implementation for S3 class

Iñaki Úcar
2017-09-22 19:04 GMT+02:00 Michael Lawrence <[hidden email]>:

> 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

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 method implementation for S3 class

Michael Lawrence-3
On Fri, Sep 22, 2017 at 10:28 AM, Iñaki Úcar <[hidden email]> wrote:

> 2017-09-22 19:04 GMT+02:00 Michael Lawrence <[hidden email]>:
>> 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
>

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 method implementation for S3 class

Iñaki Úcar
> 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

Thanks 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

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 method implementation for S3 class

Joris FA Meys
You can always check eg the Matrix package to show you how it's done.

Cheers
Joris

On Sat, Sep 23, 2017 at 6:17 PM, Iñaki Úcar <[hidden email]> wrote:

> > 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
>
> Thanks 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
>
> ______________________________________________
> [hidden email] mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel
>



--
-----------

Biowiskundedagen 2017-2018
http://www.biowiskundedagen.ugent.be/

-----------

Joris Meys
Statistical consultant

Ghent University
Faculty of Bioscience Engineering
Department of Mathematical Modelling, Statistics and Bio-Informatics

tel :  +32 (0)9 264 61 79
[hidden email]
-------------------------------
Disclaimer : http://helpdesk.ugent.be/e-maildisclaimer.php

        [[alternative HTML version deleted]]

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel