Is there a programming language that meets these requirements? - reflection

I have a few requirements before implementing my next program - hopefully a programming language exists that can do the following:
Given a class (or interface) C, the programming language allows the user access to a list of all classes which extend/implement C.
The programming language allows the user to iterate through all the variables and methods of a class.
The user is able to determine the number and types of arguments a function will take.
eg. foo(int a, String b, int c) can be queried
to return 3 or [int, String, int]
Are these absurd requirements or does some language implement them as basic techniques of reflection?

I know that you prefer a statically-typed language, but if you consider a dynamically-typed one Smalltalk may be a good fit, since everything is an object (classes and methods are no exception ot this rule) and thus everything can be manipulated (not only queried, but also changed). Going to your requirements:
Given a class (or interface) C, the programming language allows the
user access to a list of all classes which extend/implement C.
In Smalltalk there is no built-in notion of interface (though I think that I've seen extensions that added support for it). However, you can:
Given a class, find it direct subclasses: Number subclasses answers {Fraction. Float. Integer}.
Or all the hierarchy under it: Number allSubclasses answers an OrderedCollection(Fraction Float Integer ScaledDecimal SmallInteger LargePositiveInteger LargeNegativeInteger)
You can also find all classes that implement a given selector (pop in this case):
SystemNavigation default allClassesImplementing: #pop answers {ContextPart. FileSystemGuide. LIFOQueue. Stack}
As you can see, defining an "Interface" object to query for classes that implement a set of methods is quite easy (just have a collection of method names and query for classes implementing each of them, adding the classes to a set). However if you want to explicitly state in the class that it implements an interface, then you'll need to do more work.
The programming language allows the user to iterate through all the
variables and methods of a class.
Point instVarNames answers #('x' 'y')
Point allMethods answers a collection of CompiledMethods (the object that represents a method)
Point allSelectors answers a collection of all the method names that an instance of that class can answer to.
The user is able to determine the number and types of arguments a
function will take.
In this case you interact with compiled methods and ask them for the number of arguments they need (there is no notion of parameter type):
(Point methodNamed: #x) numArgs answers 0, since it is just a getter.
(Point methodNamed: #+) numArgs answers 1
This is just a small preview of the reflective capabilities of Smalltalk; if you want to go deeper you can check out some of these links:
Reflective Facilities in Smalltalk-80
Evaluating message passing control techniques in Smalltalk
Debugging Objects
HTH

I would expect most Lisp systems (Scheme, CommonLisp, ...) to meet these requirements.

Java can do this. Yet, note that no language will do:
Given a class (or interface) C, the programming language allows the
user access to a list of all classes which extend/implement C
The reason is that the number of classes that extend C is either zero (in case of final classes) or infinite. In the latter case, which is the norm, only a tiny portion of all classes that extend C has actually been written down and compiled, and only those you can access.

Related

Changing method dispatch in Common Lisp

I'm trying to simulate something akin to Haskell's typeclasses with Common Lisp's CLOS. That is, I'd like to be able to dispatch a method on an object's "typeclasses" instead of its superclasses.
I have a metaclass defined for classes which have and implement typeclasses(which are just other classes). Those classes(those that implement typeclasses) have a slot containing the list of the typeclasses they implement.
I'd like to be able to define methods for a typeclass, and then be able to dispatch that method on objects whose class implement that typeclass. And I'd like to be able to add and remove typeclasses dynamically.
I figure I could probably do this by changing the method dispatch algorithm, though that doesn't seem too simple.
Anybody is comfortable enough with CLOS and the MOP to give me some suggestions?
Thanks.
Edit: My question might be specified as, how do I implement compute-applicable-methods-using-classes and compute-applicable-methods for a "custom" generic-function class such that if some of the specializers of a generic function method are typeclasses(classes whose metaclass is the 'typeclass' class), then the corresponding argument's class must implement the typeclass(which simply means having the typeclass stored in a slot of the argument's class) for the method to be applicable?
From what I understand from documentation, when a generic function is called, compute-discriminating-functionis first called, which will first attempt to obtain applicable methods through compute-applicable-methods-using-classes, and if unsuccessful, will try the same with compute-applicable-methods.
While my definition of compute-applicable-methods-using-classes seems to work, the generic function fails to dispatch an applicable function. So the problem must be in compute-discriminating-function or compute-effective-method.
See code.
This is not easily achievable in Common Lisp.
In Common Lisp, operations (generic functions) are separate from types (classes), i.e. they're not "owned" by types. Their dispatch is done at runtime, with the possibility of adding, modifying and removing methods at runtime as well.
Usually, errors from missing methods are signaled only at runtime. The compiler has no way to know if a generic function is being "well" used or not.
The idiomatic way in Common Lisp is to use generic functions and describe its requirements, or in other words, the closest to an interface in Common Lisp is a set of generic functions and a marker mixin class. But most usually, only a protocol is specified, and its dependencies on other protocols. See, for instance, the CLIM specification.
As for type classes, it's a key feature that keeps the language not only fully type-safe, but also makes it very extensible in that aspect. Otherwise, either the type system would be too strict, or the lack of expressiveness would lead to type-unsafe situations, at least from the compiler's point of view. Note that Haskell doesn't keep, or doesn't have to keep, object types at runtime, it takes every type inference at compile-time, much in contrast with idiomatic Common Lisp.
To have something similar to type classes in Common Lisp at runtime, you have a few choices
Should you choose to support type classes with its rules, I suggest you use the meta-object protocol:
Define a new generic function meta-class (i.e. one which inherits from standard-generic-function)
Specialize compute-applicable-methods-using-classes to return false as a second value, because classes in Common Lisp are represented solely by their name, they're not "parameterizable" or "constrainable"
Specialize compute-applicable-methods to inspect the argument's meta-classes for types or rules, dispatch accordingly and possibly memoize results
Should you choose to only have parameterizable types (e.g. templates, generics), an existing option is the Lisp Interface Library, where you pass around an object that implements a particular strategy using a protocol. However, I see this mostly as an implementation of the strategy pattern, or an explicit inversion of control, rather than actual parameterizable types.
For actual parameterizable types, you could define abstract unparameterized classes from which you'd intern concrete instances with funny names, e.g. lib1:collection<lib2:object>, where collection is the abstract class defined in the lib1 package, and the lib2:object is actually part of the name as is for a concrete class.
The benefit of this last approach is that you could use these classes and names anywhere in CLOS.
The main disadvantage is that you must still generate concrete classes, so you'd probably have your own defmethod-like macro that would expand into code that uses a find-class-like function which knows how to do this. Thus breaking a significant part of the benefit I just mentioned, or otherwise you should follow the discipline of defining every concrete class in your own library before using them as specializers.
Another disadvantage is that without further non-trivial plumbing, this is too static, not really generic, as it doesn't take into account that e.g. lib1:collection<lib2:subobject> could be a subclass of lib1:collection<lib2:object> or vice-versa. Generically, it doesn't take into account what is known in computer science as covariance and contravariance.
But you could implement it: lib:collection<in out> could represent the abstract class with one contravariant argument and one covariant argument. The hard part would be generating and maintaining the relationships between concrete classes, if at all possible.
In general, a compile-time approach would be more appropriate at the Lisp implementation level. Such Lisp would most probably not be a Common Lisp. One thing you could do is to have a Lisp-like syntax for Haskell. The full meta-circle of it would be to make it totally type-safe at the macro-expansion level, e.g. generating compile-time type errors for macros themselves instead of only for the code they generate.
EDIT: After your question's edit, I must say that compute-applicable-methods-using-classes must return nil as a second value whenever there is a type class specializer in a method. You can call-next-method otherwise.
This is different than there being a type class specializer in an applicable method. Remember that CLOS doesn't know anything about type classes, so by returning something from c-a-m-u-c with a true second value, you're saying it's OK to memoize (cache) given the class alone.
You must really specialize compute-applicable-methods for proper type class dispatching. If there is opportunity for memoization (caching), you must do so yourself here.
I believe you'll need to override compute-applicable-methods and/or compute-applicable-methods-using-classes which compute the list of methods that will be needed to implement a generic function call. You'll then likely need to override compute-effective-method which combines that list and a few other things into a function which can be called at runtime to perform the method call.
I really recommend reading The Art of the Metaobject Protocol (as was already mentioned) which goes into great detail about this. To summarize, however, assume you have a method foo defined on some classes (the classes need not be related in any way). Evaluating the lisp code (foo obj) calls the function returned by compute-effective-method which examines the arguments in order to determine which methods to call, and then calls them. The purpose of compute-effective-method is to eliminate as much of the run-time cost of this as is possible, by compiling the type tests into a case statement or other conditional. The Lisp runtime thus does not have to query for the list of all methods each time you make a method call, but only when you add, remove or change a method implementation. Usually all of that is done once at load time and then saved into your lisp image for even better performance while still allowing you to change these things without stopping the system.

Rationale behind Ada encapsulation of dynamically dispatching operations (primitives)

In Ada, Primitive operations of a type T can only be defined in the package where T is defined. For example, if a Vehicules package defines Car and Bike tagged record, both inheriting a common Vehicle abstract tagged type, then all operations than can dispatch on the class-wide Vehicle'Class type must be defined in this Vehicles package.
Let's say that you do not want to add primitive operations: you do not have the permission to edit the source file, or you do not want to clutter the package with unrelated features.
Then, you cannot define operations in other packages that implicitely dispatches on type Vehicle'Class.
For example, you may want to serialize vehicles (define a Vehicles_XML package with a To_Xml dispatching function) or display them as UI elements (define a Vehicles_GTK package with Get_Label, Get_Icon, ... dispatching functions), etc.
The only way to perform dynamic dispatch is to write the code explicitely; for example, inside Vechicle_XML:
if V in Car'Class then
return Car_XML (Car (V));
else
if V in Bike'Class then
return Bike_XML (Bike (V));
else
raise Constraint_Error
with "Vehicle_XML is only defined for Car and Bike."
end if;
(And a Visitor pattern defined in Vehicles and used elsewhere would work, of course, but that still requires the same kind of explicit dispatching code. edit in fact, no, but there is still some boilerplate code to write)
My question is then:
is there a reason why operations dynamically dispatching on T are restricted to be defined in the defining package of T?
Is this intentional? Is there some historical reasons behind this?
Thanks
EDIT:
Thanks for the current answers: basically, it seems that it is a matter of language implementation (freezing rules/virtual tables).
I agree that compilers are developped incrementally over time and that not all features fit nicely in an existing tool.
As such, isolating dispatching operators in a unique package seems to be a decision mostly guided by existing implementations than by language design. Other languages outside of the C++/Java family provide dynamic dispatch without such requirement (e.g. OCaml, Lisp (CLOS); if that matters, those are also compiled languages, or more precisely, language for which compilers exist).
When I asked this question, I wanted to know if there were more fundamental reasons, at language specification level, behind this part of Ada specifications (otherwise, does it really mean that the specification assumes/enforces a particular implementation of dynamic disapatch?)
Ideally, I am looking for an authoritative source, like a rationale or guideline section in Reference Manuals, or any kind of archived discussion about this specific part of the language.
I can think of several reasons:
(1) Your example has Car and Bike defined in the same package, both derived from Vehicles. However, that's not the "normal" use case, in my experience; it's more common to define each derived type in its own package. (Which I think is close to how "classes" are used in other compiled languages.) And note also that it's not uncommon to define new derived types afterwards. That's one of the whole points of object-oriented programming, to facilitate reuse; and it's a good thing if, when designing a new feature, you can find some existing type that you can derive from, and reuse its features.
So suppose you have your Vehicles package that defines Vehicle, Car, and Bike. Now in some other package V2, you want to define a new dispatching operation on a Vehicle. For this to work, you have to provide the overriding operations for Car and Bike, with their bodies; and assuming you are not allowed to modify Vehicles, then the language designers have to decide where the bodies of the new operation have to be. Presumably, you'd have to write them in V2. (One consequence is that the body that you write in V2 would not have access to the private part of Vehicles, and therefore it couldn't access implementation details of Car or Bike; so you could only write the body of that operation if terms of already-defined operations.) So then the question is: does V2 need to provide operations for all types that are derived from Vehicle? What about types derived from Vehicle that don't become part of the final program (maybe they're derived to be used in someone else's project)? What about types derived from Vehicle that haven't yet been defined (see preceding paragraph)? In theory, I suppose this could be made to work by checking everything at link time. However, that would be a major paradigm change for the language. It's not something that could be easily. (It's pretty common, by the way, for programmers to think "it would be nice to add feature X to a language, and it shouldn't be too hard because X is simple to talk about", without realizing just what a vast impact such a "simple" feature would have.)
(2) A practical reason has to do with how dispatching is implemented. Typically, it's done with a vector of procedure/function pointers. (I don't know for sure what the exact implementation is in all cases, but I think this is basically the case for every Ada compiler as well as for C++ and Java compilers, and probably C#.) What this means is that when you define a tagged type (or a class, in other languages), the compiler will set up a vector of pointers, and based on how many operations are defined for the type, say N, it will reserve slots 1..N in the vector for the addresses of the subprograms. If a type is derived from that type and defines overriding subprograms, the derived type gets its own vector, where slots 1..N will be pointers to the actual overriding subprograms. Then, when calling a dispatching subprogram, a program can look up the address in some known slot index assigned to that subprogram, and it will jump to the correct address depending on the object's actual type. If a derived type defines new primitive subprograms, new slots are assigned N+1..N2, and types derived from that could define new subprograms that get slots N2+1..N3, and so on.
Adding new dispatching subprograms to Vehicle would interfere with this. Since new types have been derived from Vehicle, you can't insert a new area into the vector after N, because code has already been generated that assumes the slots starting at N+1 have been assigned to new operations derived for derived types. And since we may not know all the types that have been derived from Vehicle and we don't know what other types will be derived from Vehicle in the future and how many new operations will be defined for them, it's hard to pick some other location in the vector that could be used for the new operations. Again, this could be done if all of the slot assignment were deferred until link time, but that would be a major paradigm change, again.
To be honest, I can think of other ways to make this work, by adding new operations not in the "main" dispatch vector but in an auxiliary one; dispatching would probably require a search for the correct vector (perhaps using an ID assigned to the package that defines the new operations). Also, adding interface types to Ada 2005 has already complicated the simple vector implementation somewhat. But I do think this (i.e. it doesn't fit into the model) is one reason why the ability to add new dispatching operations like you suggest isn't present in Ada (or in any other compiled language that I know of).
Without having checked the rationale for Ada 95 (where tagged types were introduced), I am pretty sure the freezing rules for tagged types are derived from the simple requirement that all objects in T'Class should have all the dispatching operations of type T.
To fulfill that requirement, you have to freeze type and say that no more dispatching operations can be added to type T once you:
Derive a type from T, or
Are at the end of the package specification where T was declared.
If you didn't do that, you could have a type derived from type T (i.e. in T'Class), which hadn't inherited all the dispatching operations of type T. If you passed an object of that type as a T'Class parameter to a subprogram, which knew of one more dispatching operation on type T, a call to that operation would have to fail. - We wouldn't want that to happen.
Answering your extended question:
Ada comes with both a Reference Manual (the ISO standard), a Rationale and an Annotated Reference Manual. And a large part of the discussions behind these documents are public as well.
For Ada 2012 see http://www.adaic.org/ada-resources/standards/ada12/
Tagged types (dynamic dispatching) was introduced in Ada 95. The documents related to that version of the standard can be found at http://www.adaic.org/ada-resources/standards/ada-95-documents/

What is the difference between the approaches below in collections

Approach one : creating object of the subclass through reference of base class.
Approach Two : creating object of the subclass through reference of same class.
List<Point> objOne = new ArrayList<Point>();
ArrayList<Point> objTwo = new ArrayList<Point>();
List is an Interface even. It is more abstract and defines exactly but not more, the API properties. The implementation can differ in Java: ArrayList for bulk work, LinkedList for just a couple, saving on memory. This implementation decision should be hidden.
Functions should operate on List more than on a specific implementation, say ArrayList. It is also more generally formulated if one talks about Lists instead of ArrayLists. So also for variables I would not overspecify their type.
In many (scripting) languages like VB, PHP and others this distinction does not exist, and there is one type with one implementation. This simplifies their language and might appeal to some, but Java has a nice technical side.
You can play with different implementations, dynamically elect one with a factory method. Mock the implementation in a unit test.
List is an interface while ArrayList is a Class.
So lets suppose your co-worker wrote a function which takes in a list of points and does some work on it.
Now he used abstraction like this so that his code could work on both ArrayList as well as LinkedList.
public void myfunction(List<Point> pointlist){
//do something on the point
}
Now your first object objOne will work with this function because it has used Abstraction. This is how good code should be written.
Now your second object objTwo will not work with this function and so this kind of code should be avoided.

Is the concept of Algebraic Data Type akin to Class definitions in OO languages?

Both concepts allow new data types to be created.
The only difference I can see is that in functional languages, one can perform pattern matching on algebraic data types. But there is no comparable succinct feature for OO languages. Is this an accurate statement ?
Algebraic data types are so named because they form an "initial algebra",
+ represents sum types (disjoint unions, e.g. Either).
• represents product types (e.g. structs or tuples)
X for the singleton type (e.g. data X a = X a)
1 for the unit type ()
and μ for the least fixed point (e.g. recursive types), usually implicit.
from these operators all regular data types can be constructed.
Algebraic data types also support parametric polymophism -- meaning they can be used as constainers for any underlying type, with static guarantees of safety. Additionally, ADTs are provided with uniform syntax for introducing and eliminating data types (via constructors and pattern matching). E.g.
-- this defines a tree
data Tree a = Empty | Node a (Tree a) (Tree a)
-- this constructs a tree
let x = Node 1 (Node 2 Empty) Empty
-- this deconstructs a tree
f (Node a l r) = a + (f l) + (f r)
The richness and uniformity of algebraic data types, along with the fact they're immutable, distinguish them from OO objects, which largely:
only represent product types (so no recursive or sum-types)
do not support pattern matching
are mutable
do not support parametric polymorphism
I can see three major differences between algebraic data types and OO-style classes, not counting (im)mutablility because that varies.
Algebraic data types allows sums as well as products, whereas OO-style classes only allow products.
OO-style classes allow you to bundle a complex data item with it's accepted operations, whereas algebraic data types don't.
Algebraic data types don't distinguish between the data passed to the constructor and the data stored in the resulting value, whereas OO-style classes do (or can).
One thing I deliberately left out of that list was subtyping. While the vast majority of OO languages allow you to subclass (non-final, non-sealed, currently accessible) classes, and the vast majority of generally ML-family functional languages do not, it is clearly possible to forbid inheritance completely in a hypothetical OO (or at least OO-like) language, and it is likewise possible to produce subtyping and supertyping in algebraic data types; for a limited example of the latter, see this page on O'Haskell, which has been succeeded by Timber
A class is more than just a type definition -- classes in most OO languages are really kitchen sink features that provide all sorts of loosely related functionality.
In particular, classes act as a kind of module, giving you data abstraction and namespacing. Algebraic data types don't have this built in, modularity is usually provided as a separate, orthogonal feature (usually modules).
In some sense one can see it this way. Every language has only so many mechanisms to create user defined types. In functional (ML, Haskell style) languages, the only one is creation of an ADT. (Haskell's newtype can be seen as a degenerate case of an ADT). In OO languages, it's classes. In procedural languages it is struct or record.
It goes without saying that the semantics of a user defined data type vary from language to language, and much more so from language in paradigm#1 to language in paradigm#2. #Pharien's Flame has already outlined typical differences.
Is the concept of Algebraic Data Type akin to Class definitions in OO languages?
in functional languages, one can perform pattern matching on algebraic data types. But there is no comparable succinct feature for OO languages. Is this an accurate statement ?
That is a part of it.
As Andreas said, classes are a kitchen sink feature in statically-typed object oriented languages derived from Simula like C++, Java and C#. Classes are a jack of all trades but master of none feature in this respect: they solve many problems badly.
Comparing vanilla ML and Haskell with OO as seen in C++, Java and C# you find:
Classes can contain other classes whereas algebraic datatypes can refer to each other but cannot contain the definitions of each other.
Class hierarchies can be arbitrarily deep whereas algebraic datatypes are one-level deep: the type contains its type constructors and that is it.
New classes can be derived from old classes so classes are extensible types whereas algebraic datatypes are usually (but not always) closed.
So ADTs are not really "akin" to classes because they only solve one specific problem: class hierarchies that are one level deep. In this sense we can see two approximate observations:
ADTs require composition over inheritance.
Classes make it easy to extend a type but hard to extend the set of member functions whereas ADTs make it easy to extend functions over the type but hard to extend the type.
You might also look at the GoF design patterns. They have been expressed using classes in C++. The functional equivalents are not always ADTs but, rather, things like lambda functions instead of the command pattern and higher-order functions like map and fold instead of the visitor pattern and so on.

What does "S3 methods" mean in R?

Since I am fairly new to R, I do not know what the S3 methods and objects are. I found that there are S3 and S4 object systems, and some recommend to use S3 over S4 if possible (See Google's R Style Guide at http://google-styleguide.googlecode.com/svn/trunk/google-r-style.html)*. However, I do not know the exact definition of S3 methods/objects.
Update: As of 2019, Google's R Style Guide hyperlink is now here.
Most of the relevant information can be found by looking at ?S3 or ?UseMethod, but in a nutshell:
S3 refers to a scheme of method dispatching. If you've used R for a while, you'll notice that there are print, predict and summary methods for a lot of different kinds of objects.
In S3, this works by:
setting the class of objects of
interest (e.g.: the return value of a
call to method glm has class glm)
providing a method with the general
name (e.g. print), then a dot, and
then the classname (e.g.:
print.glm)
some preparation has to have been
done to this general name (print)
for this to work, but if you're
simply looking to conform yourself to
existing method names, you don't need
this (see the help I refered to
earlier if you do).
To the eye of the beholder, and particularly, the user of your newly created funky model fitting package, it is much more convenient to be able to type predict(myfit, type="class") than predict.mykindoffit(myfit, type="class").
There is quite a bit more to it, but this should get you started. There are quite a few disadvantages to this way of dispatching methods based upon an attribute (class) of objects (and C purists probably lie awake at night in horror of it), but for a lot of situations, it works decently. With the current version of R, newer ways have been implemented (S4 and reference classes), but most people still (only) use S3.
To get you started with S3, look at the code for the median function. Typing median at the command prompt reveals that it has one line in its body, namely
UseMethod("median")
That means that it is an S3 method. In other words, you can have a different median function for different S3 classes. To list all the possible median methods, type
methods(median) #actually not that interesting.
In this case, there's only one method, the default, which is called for anything. You can see the code for that by typing
median.default
A much more interesting example is the print function, which has many different methods.
methods(print) #very exciting
Notice that some of the methods have *s next to their name. That means that they are hidden inside some package's namespace. Use find to find out which package they are in. For example
find("acf") #it's in the stats package
stats:::print.acf
From http://adv-r.had.co.nz/OO-essentials.html:
R’s three OO systems differ in how classes and methods are defined:
S3 implements a style of OO programming called generic-function OO.
This is different from most programming languages, like Java, C++ and
C#, which implement message-passing OO. With message-passing, messages
(methods) are sent to objects and the object determines which function
to call. Typically, this object has a special appearance in the method
call, usually appearing before the name of the method/message: e.g.
canvas.drawRect("blue"). S3 is different. While computations are still
carried out via methods, a special type of function called a generic
function decides which method to call, e.g., drawRect(canvas, "blue").
S3 is a very casual system. It has no formal definition of classes.
S4 works similarly to S3, but is more formal. There are two major
differences to S3. S4 has formal class definitions, which describe the
representation and inheritance for each class, and has special helper
functions for defining generics and methods. S4 also has multiple
dispatch, which means that generic functions can pick methods based on
the class of any number of arguments, not just one.
Reference classes, called RC for short, are quite different from S3
and S4. RC implements message-passing OO, so methods belong to
classes, not functions. $ is used to separate objects and methods, so
method calls look like canvas$drawRect("blue"). RC objects are also
mutable: they don’t use R’s usual copy-on-modify semantics, but are
modified in place. This makes them harder to reason about, but allows
them to solve problems that are difficult to solve with S3 or S4.
There’s also one other system that’s not quite OO, but it’s important
to mention here:
base types, the internal C-level types that underlie the other OO
systems. Base types are mostly manipulated using C code, but they’re
important to know about because they provide the building blocks for
the other OO systems.
I came to this question mostly wondering where the names came from. It appears from this wikipedia article that the name refers to the version of the S Programming Language that R is based on. The method dispatching schemes described in the other answers come from S and are labelled appropriately according to version.
Try
methods(residuals)
which lists, among others, "residuals.lm" and "residuals.glm". This means when you have fitted a linear model, m, and type residuals(m), residuals.lm will be called. When you have fitted a generalized linear model, residuals.glm will be called.
It's kind of the C++ object model turned upside down. In C++, you define a base class having virtual functions, which are overrided by derived classed.
In R you define a virtual (aka generic) function and then you decide which classes will override this function (aka define a method). Note that the classes doing this do not need to be derived from one common super class.
I would not agree to generally prefer S3 over S4. S4 has more formalism (= more typing) and this may be too much for some applications. S4 classes, however, can be de defined like a class or struct in C++. You can specify that an object of a certain class is made up of a string and two numbers for example:
setClass("myClass", representation(label = "character", x = "numeric", y = "numeric"))
Methods that are called with an object of that class can rely on the object having those members. That's very different from S3 classes, which are just a list of a bunch of elements.
With S3 and S4, you call a member function by fun(object, args) and not by object$fun(args). If you are looking for something like the latter, have a look at the proto package.
Here is an updated fast rundown of the numerous R object systems according to "Advanced R, 2nd edition" (CRC Press, 2019) by Hadley Wickham (Chief Scientist at RStudio), which has a web representation here, based on the chapter about Object-Oriented Programming.
The first edition from 2015 has a web representation here, with the corresponding chapter on OO here.
Approaches to OO systems
Hadley defines the following to distinguish two distinct approaches to OO programming:
Functional OOP: methods (callable code pieces) belong to generic functions (not to be confused with Java/C# generic methods). Think of the methods as being located in a global lookup table. The method to execute is found by the runtime system based on the name of the function and the type (or object class) of one or more arguments passed to that function (this is called "method dispatch"). Syntax-wise, method calls may look like ordinary function calls: myfunc(object, arg1, arg2). This call would lead the runtime to look for the method associated to the pair ("myfunc", typeof(object)) or possibly ("myfunc", typeof(object), typeof(arg1), typeof(arg2)) if the language supports that. In R's S3, the full name of the generic function gives the (function-name, class) pair. For example: mean.Date is the method to compute the mean of Dates. Try methods("mean") to list the generic methods with function name mean. The Functional OOP approach is found for example in the OO pioneer Smalltalk, the Common Lisp Object System and Julia. Hadley notes that "Compared to R, Julia’s implementation is fully developed and extremely performant."
Encapsulated OOP: methods belong to objects or classes, and method calls typically look like object.method(arg1, arg2). This is called encapsulated because the object encapsulates both data (fields) and behaviour (methods). Think of the method as being located in a lookup table attached to the object or the object's class description. The runtime looks the method up based on method name and possibly the type of one or more arguments. This is the approach found in "popular" OO languages like C++, Java, C#.
In both cases, if inheritance is supported (it probably is), the runtime may traverse the class hierarchy upwards until it has found a match for the call lookup key.
How to find out what system an R object belongs to
library(sloop) # formerly, "pryr"
otype(mtcars)
#> [1] "S3"
The R object systems
S3
Functional OOP approach.
Most important system according to Hadley.
Simplest, most common. First OO system used by R.
Comes with base R, used throughout base R.
Relies on conventions rather than enforced guarantees.
See Chambers, John M, and Trevor J Hastie. 1992. "Statistical Models in S." Wadsworth & Brooks/Cole Advanced Books & Software.
Details in "Advanced R, 2nd edition" here.
S4
Functional OOP approach.
Third most important system according to Hadley.
Rewrite of S3, therefore similar to S3, but more formal and more strict: it forces you to think carefully about program design. Suited for building large systems (e.g. the Bioconductor project).
Implemented in the base "methods" package.
See: Chambers, John M. 1998. "Programming with Data: A Guide to the S Language." Springer.
Details in "Advanced R, 2nd edition" here.
RC aka "Reference Classes"
Encapsulated OOP approach.
Comes with base R.
Based on S4.
RC objects are special type of S4 objects that are also "mutable". i.e. instead of using R's usual copy-on-modify semantics, they can be modified in-place. Note that mutable state is hard to reason about and a source of ugly bugs but can lead to more efficient code in certain applications.
R6
Encapsulated OOP approach.
Second most important system according to Hadley.
Can be found in the R6 package (install with library(R6))
Similar to RC, but lighter & much faster: it does not depend on S4 or the methods package. Built on top of R environments. Also has:
public and private methods
active bindings (fields, that, when accessed, actually call a method)
class inhertance which works across packages
both class methods (code that belongs to class and can access an instance via self, private, super) and member functions (functions assigned to fields, but which are not methods, just functions)
Provides a standardised way to escape R's "copy-on-modify" semantics
See the package site: "R6: Encapsulated object-oriented programming for R".
Details in "Advanced R, 2nd edition" here.
Others
There are others, like R.oo (similar to RC), proto (prototype-based, think JavaScript) and Mutatr. However, "Advanced R" says:
Apart from R6, which is widely used, these systems are primarily of
theoretical interest. They do have their strengths, but few R users
know and understand them, so it is hard for others to read and
contribute to your code.
Be sure to read the chapter on trade-offs in "Advanced R, 2nd edition", too.

Resources