ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 06.04.2021
Просмотров: 908
Скачиваний: 1
7.2. S4
CIRCLE 7. TRIPPING ON OBJECT ORIENTATION
A list of all methods for
median
(in the current session) is found with:
methods(median)
and methods for the
"factor"
class are found with:
methods(class=’factor’)
7.1.3
inheritance
Classes can inherit from other classes. For example:
>
class(ordered(c(90, 90, 100, 110, 110)))
[1] "ordered" "factor"
Class
"ordered"
inherits from class
"factor"
. Ordered factors are factors, but
not all factors are ordered. If there is a method for
"ordered"
for a specific
generic, then that method will be used when the argument is of class
"ordered"
.
However, if there is not a method for
"ordered"
but there is one for
"factor"
,
then the method for
"factor"
will be used.
Inheritance should be based on similarity of the structure of the objects,
not similarity of the concepts for the objects. Matrices and data frames have
similar concepts. Matrices are a specialization of data frames (all columns of the
same type), so conceptually inheritance makes sense. However, matrices and
data frames have completely different implementations, so inheritance makes
no practical sense. The power of inheritance is the ability to (essentially) reuse
code.
7.2
S4 methods
S4 methods correspond to the green book.
S3 methods are simple and powerful, and a bit
ad hoc
. S4 methods remove
the
ad hoc
—they are more strict and more general. The S4 methods technology
is a stiffer rope—when you hang yourself with it, it surely will not break. But
that is basically the point of it—the programmer is restricted in order to make
the results more dependable for the user. That’s the plan anyway, and it often
works.
7.2.1
multiple dispatch
One feature of S4 methods that is missing from S3 methods (and many other
object-oriented systems) is multiple dispatch. Suppose you have an object of
class
"foo"
and an object of class
"bar"
and want to perform function
fun
on
these objects. The result of
fun(foo, bar)
40
7.2. S4
CIRCLE 7. TRIPPING ON OBJECT ORIENTATION
Figure 7.1: The Simoniacs by Sandro Botticelli.
may or may not to be different from
fun(bar, foo)
If there are many classes or many arguments to the function that are sensitive
to class, there can be big complications. S4 methods make this complicated
situation relatively simple.
We saw that
UseMethod
creates an S3 generic function. S4 generic functions
are created with
standardGeneric
.
7.2.2
S4 structure
S4 is quite strict about what an object of a specific class looks like. In contrast
S3 methods allow you to merely add a
class
attribute to any object—as long
as a method doesn’t run into anything untoward, there is no penalty. A key
advantage in strictly regulating the structure of objects in a particular class is
that those objects can be used in C code (via the
.Call
function) without a
copious amount of checking.
Along with the strictures on S4 objects comes some new vocabulary. The
pieces (components) of the object are called
slots
. Slots are accessed by the
8
@
8
operator. So if you see code like:
41
7.3. NSPACES
CIRCLE 7. TRIPPING ON OBJECT ORIENTATION
x@Data
that is an indication that
x
is an S4 object.
By now you will have noticed that S4 methods are driven by the
class
attribute just as S3 methods are. This commonality perhaps makes the two
systems appear more similar than they are. In S3 the decision of what method
to use is made in real-time when the function is called. In S4 the decision is
made when the code is loaded into the R session—there is a table that charts
the relationships of all the classes. The
showMethods
function is useful to see
the layout.
S4 has inheritance, as does S3. But, again, there are subtle differences. For
example, a concept in S4 that doesn’t resonate in S3 is
contains
. If S4 class
"B"
has all of the slots that are in class
"A"
, then class
"B"
contains class
"A"
.
7.2.3
discussion
Will S4 ever totally supplant S3? Highly unlikely. One reason is backward
compatibility—there is a whole lot of code that depends on S3 methods. Addi-
tionally, S3 methods are convenient. It is very easy to create a
plot
or
summary
method for a specific computation (a simulation, perhaps) that expedites anal-
ysis.
So basically S3 and S4 serve different purposes. S4 is useful for large,
industrial-strength projects. S3 is useful for
ad hoc
projects.
If you are planning on writing S4 (or even S3) methods, then you can defi-
nitely do worse than getting the book
Software for Data Analysis: Programming
with R
by John Chambers. Don’t misunderstand: this book can be useful even
if you are not using methods.
Two styles of object orientation are hardly enough. Luckily, there are the
OOP
,
R.oo
and
proto
packages that provide three more.
7.3
Namespaces
Namespaces don’t really have much to do with object-orientation. To the casual
user they are related in that both seem like an unwarranted complication. They
are also related in the sense that that seeming complexity is actually simplicity
in disguise.
Suppose that two packages have a function called
recode
. You want to use
a particular one of these two. There is no guarantee that the one you want will
always be first on the search list. That is the problem for which namespaces are
the answer.
To understand namespaces, let’s consider an analogy of a function that re-
turns a named list. There are some things in the environment of the function
that you get to see (the components that it returns), and possibly some objects
that you can’t see (the objects created in the function but not returned). A
42
7.3. NSPACES
CIRCLE 7. TRIPPING ON OBJECT ORIENTATION
namespace exports one or more objects so that they are visible, but may have
some objects that are private.
The way to specify an object from a particular namespace is to use the
8
::
8
operator:
>
stats::coef
function (object, ...)
UseMethod("coef")
<environment: namespace:stats>
This operator fails if the name is not exported:
>
stats::coef.default
Error: ’coef.default’ is not an exported object
from ’namespace:stats’
There are ways to get the non-exported objects, but you have to promise not to
use them except to inspect the objects. You can use
8
:::
8
or the
getAnywhere
function:
>
stats:::coef.default
function (object, ...)
object$coefficients
<environment: namespace:stats>
>
getAnywhere(’coef.default’)
A single object matching ’coef.default’ was found
It was found in the following places
registered S3 method for coef from namespace stats
namespace:stats
with value
function (object, ...)
object$coefficients
<environment: namespace:stats>
There can be problems if you want to modify a function that is in a namespace.
Functions
assignInNamespace
and
unlockBinding
can be useful in this regard.
The existence of namespaces, S3 methods, and especially S4 methods makes
R more suitable to large, complex applications than it would otherwise be. But
R is not the best tool for every application. And it doesn’t try to be. One of the
design goals of R is to make it easy to interact with other software to encourage
the best tool being used for each task.
43
Circle 8
Believing It Does as
Intended
In this Circle we came across the fraudulent—each trapped in their own flame.
This Circle is wider and deeper than one might hope. Reasons for this
include:
•
Backwards compatibility. There is roughly a two-decade history of com-
patibility to worry about. If you are a new user, you will think that rough
spots should be smoothed out no matter what. You will think differ-
ently if a new version of R breaks your code that has been working. The
larger splinters have been sanded down, but this still leaves a number of
annoyances to adjust to.
•
R is used both interactively and programmatically. There is tension there.
A few functions make special arrangements to make interactive use easier.
These functions tend to cause trouble if used inside a function. They can
also promote false expectations.
•
R does a lot.
In this Circle we will meet a large number of ghosts, chimeras and devils. These
can often be exorcised using the
browser
function. Put the command:
browser()
at strategic locations in your functions in order to see the state of play at those
points. A close alternative is:
recover()
44