Now we can create another shape class and pass it in when calculating the sum without breaking our code. However, now another problem arises, how do we know that the object passed into the AreaCalculator is actually a shape or if the shape has a method named area?
Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.
The program does not squawk, but when we call the HTML method on the $output2 object we get an E_NOTICE error informing us of an array to string conversion.
This is a much better approach, but a pitfall to watch out for is when type-hinting these interfaces, instead of using a ShapeInterface or a SolidShapeInterface.
Now in AreaCalculator class, we can easily replace the call to the area method with calculate and also check if the object is an instance of the ManageShapeInterface and not the ShapeInterface.
This might sound bloated, but it is really easy to understand. This principle allows for decoupling, an example that seems like the best way to explain this principle:
This chapter is a field guide for recognising and working with R`s objects in the wild. R has three object oriented systems (plus the base types), so it can be a bit intimidating. The goal of this guide is not to make you an expert in all four systems, but to help you identify which system you’re working with and to help you use it effectively.
Central to any object?oriented system are the concepts of class and method. A class defines the behaviour of objects by describing their attributes and their relationship to other classes. The class is also used when selecting methods, functions that behave differently depending on the class of their input. Classes are usually organised in a hierarchy: if a method does not exist for a child, then the parent`s method is used instead; the child inherits behaviour from the parent.
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(). 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, ). S3 is a very casual system. It has no formal definition of classes.
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(). 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.
The following sections describe each system in turn, starting with base types. You’ll learn how to recognise the OO system that an object belongs to, how method dispatch works, and how to create new objects, classes, generics, and methods for that system. The chapter concludes with a few remarks on when to use each system.
How do you tell what OO system (base, S3, S4, or RC) an object is associated with?
Base types teaches you about R`s base object system. Only R-core can add new classes to this system, but it`s important to know about because it underpins the three other systems.
S3 shows you the basics of the S3 object system. It`s the simplest and most commonly used OO system.
Underlying every R object is a C structure (or struct) that describes how that object is stored in memory. The struct includes the contents of the object, the information needed for memory management, and, most importantly for this section, a type. This is the base type of an R object. Base types are not really an object system because only the R core team can create new types. As a result, new base types are added very rarely: the most recent change, in 2011, added two exotic types that you never see in R, but are useful for diagnosing memory problems (NEWSXP and FREESXP). Prior to that, the last type added was a special base type for S4 objects (S4SXP) in 2005.
Data structures explains the most common base types (atomic vectors and lists), but base types also encompass functions, environments, and other more exotic objects likes names, calls, and promises that you’ll learn about later in the book. You can determine an object`s base type with typeof(). Unfortunately the names of base types are not used consistently throughout R, and type and the corresponding “is” function may use different names:
Functions that behave differently for different base types are almost always written in C, where dispatch occurs using switch statements (e.g., switch(TYPEOF(x))). Even if you never write C code, it`s important to understand base types because everything else is built on top of them: S3 objects can be built on top of any base type, S4 objects use a special base type, and RC objects are a combination of S4 and environments (another base type). To see if an object is a pure base type, i.e., it doesn’t also have S3, S4, or RC behaviour, check that is.object(x) returns FALSE.
Most objects that you encounter are S3 objects. But unfortunately there`s no simple way to test if an object is an S3 object in base R. The closest you can come is is.object(x) !isS4(x), i.e., it`s an object, but not S4. An easier way is to use pryr::otype():
In S3, methods belong to functions, called generic functions, or generics for short. S3 methods do not belong to objects or classes. This is different from most other programming languages, but is a legitimate OO style.
To determine if a function is an S3 generic, you can inspect its source code for a call to UseMethod(): that`s the function that figures out the correct method to call, the process of method dispatch. Similar to otype(), pryr also provides ftype() which describes the object system, if any, associated with a function:
S3 is a simple and ad hoc system; it has no formal definition of a class. To make an object an instance of a class, you just take an existing base object and set the class attribute. You can do that during creation with structure(), or after the fact with class-():
S3 objects are usually built on top of lists, or atomic vectors with attributes. (You can refresh your memory of attributes with attributes.) You can also turn functions into S3 objects. Other base types are either rarely seen in R, or have unusual semantics that don’t work well with attributes.
You can determine the class of any object using class(x), and see if an object inherits from a specific class using inherits(x, ).
The class of an S3 object can be a vector, which describes behaviour from most to least specific. For example, the class of the glm() object is c(, ) indicating that generalised linear models inherit behaviour from linear models. Class names are usually lower case, and you should avoid .. Otherwise, opinion is mixed whether to use underscores (my_class) or CamelCase (MyClass) for multi-word class names.
Apart from developer supplied constructor functions, S3 has no checks for correctness. This means you can change the class of existing objects:
If you’ve used other OO languages, this might make you feel queasy. But surprisingly, this flexibility causes few problems: while you can change the type of an object, you never should. R doesn’t protect you from yourself: you can easily shoot yourself in the foot. As long as you don’t aim the gun at your foot and pull the trigger, you won’t have a problem.
However, this is just as dangerous as changing the class of an object, so you shouldn’t do it. Please don’t point the loaded gun at your foot! The only reason to call the method directly is that sometimes you can get considerable performance improvements by skipping method dispatch. See performance for details.
You can also call an S3 generic with a non-S3 object. Non-internal S3 generics will dispatch on the implicit class of base types. (Internal generics don’t do that for performance reasons.) The rules to determine the implicit class of a base type are somewhat complex, but are shown in the function below:
Read the source code for t() and t.test() and confirm that t.test() is an S3 generic and not an S3 method. What happens if you create an object with class test and call t() with it?
All S4 related code is stored in the methods package. This package is always available when you’re running R interactively, but may not be available when running R in batch mode. For this reason, it`s a good idea to include an explicit library(methods) whenever you’re using S4.
S4 is a rich and complex system. There`s no way to explain it fully in a few pages. Here I’ll focus on the key ideas underlying S4 so you can use existing S4 objects effectively. To learn more, some good references are:
Recognising S4 objects, generics, and methods is easy. You can identify an S4 object because str() describes it as a “formal” class, isS4() returns TRUE, and pryr::otype() returns “S4”. S4 generics and methods are also easy to identify because they are S4 objects with well defined classes.
There aren’t any S4 classes in the commonly used base packages (stats, graphics, utils, datasets, and base), so we’ll start by creating an S4 object from the built-in stats4 package, which provides some S4 classes and methods associated with maximum likelihood estimation:
Use is() with one argument to list all classes that an object inherits from. Use is() with two arguments to test if an object inherits from a specific class.
You can get a list of all S4 generics with getGenerics(), and a list of all S4 classes with getClasses(). This list includes shim classes for S3 classes and base types. You can list all S4 methods with showMethods(), optionally restricting selection either by generic or by class (or both). It`s also a good idea to supply where search() to restrict the search to methods available in the global environment.
We offer object best
object best, object, best,