I started to implement a kind of numbers in R. I have a function to add them, multiply them, etc. Now I want to do a convenient interface for the arithmetic on these numbers. That is, I don't the want the user to type multiply(x, add(y, z)), but x * (y + z) instead, etc. What is the best way to achieve this in terms of efficiency, S3 or S4? I already did such an arithmetic implementation in S4 for a package (lazyNumbers), this was a bit long, a bit "verbose". Is it more comfortable in S3? I don't know how to do with S3 yet, but I'll learn if needed.
Implementing an arithmetic system in R
108 views Asked by Stéphane Laurent At
2
There are 2 answers
0
On
Just to elaborate on my comment ...
x <- structure(0, class = "zzz")
.S3method("Ops", "zzz",
function(e1, e2) {
if (missing(e1))
"A" # should never happen
else if (missing(e2))
"B"
else if (!inherits(e2, "zzz"))
"C"
else if (!inherits(e1, "zzz"))
"D"
else "E"
})
+x
## [1] "B"
x + 1
## [1] "C"
1 + x
## [1] "D"
x + x
## [1] "E"
The answer will depend on how your "numbers" operate, but I'll try to identify the strengths and weaknesses of each approach here so you can make up your own mind.
S3
class()of the first argument. So if you have an objectxof your class,x + 1and1 + xwon't call the same method. (Update: apparently, members of theOpsgroup do consider the class of both arguments, so if there is a+.myclassorOps.myclassfunction then these will still be called in the case of1+xandx+1. However, forx+ywhere there are separate methods for the class ofxandy, the default method is used, which will presumably fail.)S4
class()of all argumentsgeneric.classsetValidity(e.g. a function that always returns TRUE to skip validity checking).Also look into the group generics
Ops,Mathand so on. It may be that even if you need to use S4 that you can just write methods for these. (Rememer that+and-can be unary as well as binary though, you need to make sure that the function works as intended for the case whene1is your S4 class ande2ismissing. Depending on what sort of object your class represents, "as intended" might mean throwing an error.)In terms of efficiency, if you are spending a long time in method dispatch rather than actual calculation then you are probably doing something wrong. In particular, consider having your class represent a vector (perhaps a list if you really need to) of whatever sort of number you are working with. Once a method has been chosen, the calculation will take the same amount of time regardless of whether we used S3 or S4, with the exception that S4 will check that the object is valid at the end. The check is typically faster than the method dispatch unless the class is very complex (i.e. has a lot of slots or a deep inheritance structure).
If by "efficiency" you simply meant not writing lots of code then group generics are the best time saver. They work with both S3 and S4.
Below is a simple example of a group generic. I've used the example of a class with two slots,
xas an ordinary numeric andtimestampas the time it was calculated. We want operators to "act on thexslot" and we achieve that as follows:which produces six objects of class
timestampedNumwithxslots 5, 5, -5, 6, 6 and 10 respectively.