R already has two OO systems built-in (S3 and S4) and many additionalOO systems are available in CRAN packages. Why did we decide more workwas needed? This vignette will discuss some of the motivations behindS7, focussing on the aspects of S3 and S4 that have been found to beparticularly challenging in practice.
S3 is very informal, meaning that there’s no formal definition ofa class. This makes it impossible to know exactly which properties anobject should or could possess, or even what its parent class should be.S7 resolves this problem with a formal definition encoded in a classobject produced bynew_class(). This includes support forvalidation (and avoiding validation where needed) as inspired byS4.
When a new user encounter an S3 generic, they are often confusedbecause the implementation of the function appears to be missing. S7 hasa thoughtfully designed print method that makes it clear what methodsare available and how to find their source code.
Properties of an S3 class are usually stored in attributes, but,by default,attr() does partial matching, which can lead tobugs that are hard to diagnose. Additionally,attr()returnsNULL if an attribute doesn’t exist, so misspellingan attribute can lead to subtle bugs.@ fixes both of theseproblems.
S3 method dispatch is complicated for compatibility with S. Thiscomplexity affects relatively little code, but when you attempt to diveinto the details it makesUseMethod() hard to understand.As much as possible, S7 avoids any “funny” business with environments orpromises, so that there is no distinction between argument values andlocal values.
S3 is primarily designed for single dispatch and double dispatchis only provided for a handful of base generics. It’s not possible toreuse the implementation for user generics. S7 provides a standard wayof doing multiple dispatch (including double dispatch) that can be usedfor any generic.
NextMethod() is unpredictable since you can’t tellexactly which method will be called by only reading the code; youinstead need to know both the complete class hierarchy and what othermethods are currently registered (and loading a package might changethose methods). S7 takes a difference approach withsuper(), requiring explicit specification of the superclassto be used.
Conversion between S3 classes is only implemented via looseconvention: if you implement a classfoo, then you shouldalso provide genericas.foo() to convert other objects tothat type. S7 avoids this problem by providing the double-dispatchconvert() generic so that you only need to provide theappropriate methods.
Multiple inheritance seemed like a powerful idea at the time, butin practice it appears to generate more problems than it solves. S7 doesnot support multiple inheritance.
S4’s method dispatch uses a principled but complex distancemetric to pick the best method in the presence of ambiguity. Time hasshown that this approach is hard for people to understand and makes ithard to predict what will happen when new methods are registered. S7implements a much simpler, greedy, approach that trades some additionalwork on behalf of the class author for a system that is simpler andeasier to understand.
S4 is a clean break from S3. This made it possible to makeradical changes but it made it harder to switch from S3 to S4, leadingto a general lack of adoption in the R community. S7 is designed to bedrop-in compatible with S3, making it possible to convert existingpackages to use S7 instead of S3 with only an hour or two ofwork.
@ or@<-.Secondly, users know about@ and use it to access objectinternals even though they’re not supposed to. S7 avoids these problemsby accepting the fact that R is a data language, and that there’s no wayto stop users from pulling the data they need out of an object. To makeit possible to change the internal implementation details of an objectwhile preserving existing@ usage, S7 provides dynamicproperties.