ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 06.04.2021
Просмотров: 907
Скачиваний: 1
Circle 6
Doing Global Assignments
Heretics imprisoned in flaming tombs inhabit Circle 6.
A global assignment can be performed with
8
<<-
8
:
>
x <- 1
>
y <- 2
>
fun
function ()
{
x <- 101
y <<- 102
}
>
fun()
>
x
[1] 1
>
y
[1] 102
This is life beside a volcano.
If you think you need
8
<<-
8
, think again. If on reflection you still think you
need
8
<<-
8
, think again. Only when your boss turns
red
with anger over you
not doing anything should you temporarily give in to the temptation. There
have been proposals (no more than half-joking) to eliminate
8
<<-
8
from the
language. That would not eliminate global assignments, merely force you to use
the
assign
function to achieve them.
What’s so wrong about global assignments? Surprise.
Surprise in movies and novels is good. Surprise in computer code is bad.
Except for a few functions that clearly have side effects, it is expected in
R that a function has no side effects. A function that makes a global assign-
ment violates this expectation. To users unfamiliar with the code (and even to
the writer of the code after a few weeks) there will be an object that changes
seemingly by magic.
35
CIRCLE 6. DOING GLOBAL ASSIGNMENTS
Figure 6.1: The sowers of discord by Sandro Botticelli.
A particular case where global assignment is useful (and not so egregious)
is in memoization. This is when the results of computations are stored so that
if the same computation is desired later, the value can merely be looked up
rather than recomputed. The global variable is not so worrisome in this case
because it is not of direct interest to the user. There remains the problem of
name collisions—if you use the same variable name to remember values for two
different functions, disaster follows.
In R we can perform memoization by using a locally global variable. (“locally
global” is meant to be a bit humorous, but it succinctly describes what is going
on.) In this example of computing Fibonacci numbers, we are using the
8
<<-
8
operator but using it safely:
fibonacci <- local(
{
memo <- c(1, 1, rep(NA, 100))
f <- function(x)
{
if(x == 0) return(0)
if(x < 0) return(NA)
if(x > length(memo))
stop("’x’ too big for implementation")
if(!is.na(memo[x])) return(memo[x])
ans <- f(x-2) + f(x-1)
memo[x] <<- ans
36
CIRCLE 6. DOING GLOBAL ASSIGNMENTS
ans
}
}
)
So what is this mumbo jumbo saying? We have a function that is just imple-
menting memoization in the naive way using the
8
<<-
8
operator. But we are
hiding the memo object in the environment local to the function. And why is
fibonacci
a function? The return value of something in curly braces is what-
ever is last. When defining a function we don’t generally name the object we
are returning, but in this case we need to name the function because it is used
recursively.
Now let’s use it:
>
fibonacci(4)
[1] 3
>
head(get(’memo’, envir=environment(fibonacci)))
[1] 1 1 2 3 NA NA
From computing the Fibonacci number for 4, the third and fourth elements of
memo
have been filled in. These values will not need to be computed again, a
mere lookup suffices.
R always passes by value. It never passes by reference.
There are two types of people: those who understand the preceding para-
graph and those who don’t.
If you don’t understand it, then R is right for you—it means that R is a
safe place (notwithstanding the load of things in this document suggesting the
contrary). Translated into humanspeak it essentially says that it is dreadfully
hard to corrupt data in R. But ingenuity knows no bounds ...
If you do understand the paragraph in question, then you’ve probably al-
ready caught on that the issue is that R is heavily influenced by functional
programming—side effects are minimized. You may also worry that this implies
hideous memory inefficiency. Well, of course, the paragraph in question is a lie.
If it were literally true, then objects (which may be very large) would always
be copied when they are arguments to functions. In fact, R attempts to only
copy objects when it is necessary, such as when the object is changed inside the
function. The paragraph is conceptually true, but not literally true.
37
Circle 7
Tripping on Object
Orientation
We came upon a sinner in the seventh Circle. He said, “Below my head is the
place of those who took to simony before me—they are stuffed into the fissures
of the stone.” Indeed, with flames held to the soles of their feet.
It turns out that versions of S (of which R is a dialect) are color-coded by
the cover of books written about them. The books are: the brown book, the
blue book, the white book and the green book.
7.1
S3 methods
S3 methods correspond to the white book.
The concept in R of attributes of an object allows an exceptionally rich
set of data objects. S3 methods make the
class
attribute the driver of an
object-oriented system. It is an optional system. Only if an object has a
class
attribute do S3 methods really come into effect.
There are some functions that are
generic
. Examples include
,
plot
,
summary
. These functions look at the class attribute of their first argument. If
that argument does have a class attribute, then the generic function looks for a
method
of the generic function that matches the class of the argument. If such a
match exists, then the method function is used. If there is no matching method
or if the argument does not have a class, then the default method is used.
Let’s get specific. The
lm
(linear model) function returns an object of class
"lm"
. Among the methods for
are
print.lm
and
print.default
. The
result of a call to
lm
is printed with
print.lm
. The result of
1:10
is printed
with
print.default
.
S3 methods are simple and powerful. Objects are printed and plotted and
summarized appropriately, with no effort from the user. The user only needs to
know
,
plot
and
summary
.
38
7.1. S3
CIRCLE 7. TRIPPING ON OBJECT ORIENTATION
There is a cost to the free lunch. That
is generic means that what
you see is not what you get (sometimes). In the printing of an object you may
see a number that you want—an R-squared for example—but don’t know how
to grab that number. If your mystery number is in
obj
, then there are a few
ways to look for it:
print.default(obj)
print(unclass(obj))
str(obj)
The first two print the object as if it had no class, the last prints an outline of
the structure of the object. You can also do:
names(obj)
to see what components the object has—this can give you an overview of the
object.
7.1.1
generic functions
Once upon a time a new user was appropriately inquisitive and wanted to know
how the median function worked. So, logically, the new user types the function
name to see it:
>
median
function (x, na.rm = FALSE)
UseMethod("median")
<environment: namespace:stats>
The new user then asks, “How can I find the code for
median
?”
The answer is, “You
have
found the code for
median
.”
median
is a generic
function as evidenced by the appearance of
UseMethod
. What the new user
meant to ask was, “How can I find the default method for
median
?”
The most sure-fire way of getting the method is to use
getS3method
:
getS3method(’median’, ’default’)
7.1.2
methods
The
methods
function lists the methods of a generic function. Alternatively
given a class it returns the generic functions that have methods for the class.
This statement needs a bit of qualification:
•
It is listing what is currently attached in the session.
•
It is looking at names—it will list objects in the format of
generic.class
.
It is reasonably smart, but it can be fooled into listing an object that is
not really a method.
39