1 Function components
1.1 Primitive functions
1.1.1 What function allows you to tell if an object is a function? What function allows you to tell if a function is a primitive function?
is.primitive(sum)
TRUE
1.1.2 This code makes a list of all functions in the base package.
objs <- mget(ls("package:base"), inherits = TRUE) funs <- Filter(is.function, objs)
- Which base function has the most arguments?
- How many base functions have no arguments? What’s special about those functions?
- How could you adapt the code to find all primitive functions?
## 2a n_args <- lapply(funs, function(x) length(formals(x))) max_arg <- n_args[which.max(unlist(n_args))] print(paste(names(max_arg), "has", max_arg, "arguments"))
scan has 22 arguments
## 2b no_arg <- sum(unlist(lapply(n_args, function(x) x == 0))) print(paste(no_arg, "functions have no arguments")) ## Most of them seem to be primitive ## sum(unlist(lapply(funs, function(x) length(formals(x)) == 0 && is.primitive(x))))
225 functions have no arguments
## 2c prim.funs <- Filter(is.primitive,Filter(is.function, objs))
1.1.3 What are the three important components of a function?
## body body() # the code inside the function. ## arguments formals() # the list of arguments which controls how you can call the function. ## enivronment environment() # the “map” of the location of the function’s variables.
1.1.4 When does printing a function not show what environment it was created in?
E.g. primitive functions
2 Lexical scoping
2.1 What does the following code return? Why? What does each of the three c’s mean?
c <- 10 c(c = c)
10
- c <- 10 assign a variable
- c() combine function
- c = c assign a name to argument
2.2 What are the four principles that govern how R looks for values?
- name masking - within lexical scoping
- functions vs. variables - parenthesis causes R to ignore variables
- a fresh start - function environment is created on each call
- dynamic lookup - lookup is done on evaluation
2.3 What does the following function return? Make a prediction before running the code yourself.
f <- function(x) { f <- function(x) { f <- function(x) { x ^ 2 } f(x) + 1 } f(x) * 2 } f(10)
202
3 Every operation is a function call
4 Function arguments
4.1 Clarify the following list of odd function calls:
x <- sample(x = c(1:10, NA), size = 20, replace = TRUE) y <- runif(n = 20, min = 0, max = 1) cor(x = x, y = y, use = "pairwise.complete.obs", method = "kendall")
0.157715624693436
4.2 What does this function return? Why? Which principle does it illustrate?
f1 <- function(x = {y <- 1; 2}, y = 0) { x + y } f1()
3
- Lazy evaluation. As the default value for x is evaluated so is y. The default value for the y argument will there for not be used.
4.3 What does this function return? Why? Which principle does it illustrate?
f2 <- function(x = z) { z <- 100 x } f2()
100
- That default arguments can be defined in terms of variables created within the function.
5 Special calls
5.1 Create a list of all the replacement functions found in the base package. Which ones are primitive functions?
- Replacement functions have special names xxx<-
objs <- mget(ls("package:base"), inherits = TRUE) funs <- Filter(is.function, objs) funs_name <- names(unlist(funs)) grep(".<-$",funs_name, value = TRUE)
<<- |
[<- |
[[<- |
@<- |
$<- |
attr<- |
attributes<- |
body<- |
class<- |
colnames<- |
comment<- |
diag<- |
dim<- |
dimnames<- |
Encoding<- |
environment<- |
formals<- |
is.na<- |
length<- |
levels<- |
mode<- |
mostattributes<- |
names<- |
oldClass<- |
parent.env<- |
regmatches<- |
row.names<- |
rownames<- |
split<- |
storage.mode<- |
substr<- |
substring<- |
units<- |
5.2 What are valid names for user-created infix functions?
All user-created infix functions must start and end with %. The names of infix functions are more flexible than regular R functions: they can contain any sequence of characters (except “%”, of course).
5.3 Create an infix xor()
operator.
`%xor%` <- function(a, b) !a & b | a & !b # xor(a, b) a <- as.logical(rbinom(50, 1, 0.5)) b <- as.logical(rbinom(50, 1, 0.5)) identical(xor(a, b), a %xor% b)
TRUE
5.4 Create infix versions of the set functions intersect()
, union()
, and setdiff()
.
v1 <- letters[1:5] v2 <- letters[4:8] `%U%` <- function(a, b) unique(c(a, b)) identical(union(v1, v2), v1 %U% v2) `%∩%` <- function(a, b) b[match(a, b, 0L)] identical(intersect(v1, v2), v1 %∩% v2) `%\\%` <- function(a, b) a[!match(a, b, 0L)] identical(setdiff(v1, v2), v1 %\% v2)
TRUE
5.5 Create a replacement function that modifies a random location in a vector.
`rReplace<-` <- function(x, value) { x[sample(x, 1)] <- value x } x <- 1:10 rReplace(x) <- 42
42
6 Return values
6.1 How does the chdir parameter of source()
compare to in_dir()
? Why might you prefer one approach to the other?
The chdir
argument in source()
uses add = TRUE
, i.e. adds to previous
on.exit()
.
6.2 What function undoes the action of library()
? How do you save and restore the values of options()
and par()
?
library(ggplot2) detach("package:ggplot2", unload=TRUE) op <- options() options(op) # reset (all) initial options old.par <- par(no.readonly = TRUE) # all par settings which # could be changed. par(old.par)
6.3 Write a function that opens a graphics device, runs the supplied code, and closes the graphics device (always, regardless of whether or not the plotting code worked).
myplot <- function(code) { dev.new() on.exit(dev.off()) force(code) } myplot(plot(rnorm))
6.4 We can use on.exit()
to implement a simple version of capture.output()
.
By closing/removing the file in a on.exit( add = TRUE)
much of logic
for when to close the file appears to have been reduced.
7 Quiz
7.1 What are the three components of a function?
- body
- arguments (compare function signature)
- environment
7.2 What does the following code return?
x <- 10 f1 <- function(x) { function() { x + 10 } } f1(1)()
11
7.3 How would you more typically write this code?
`+`(1, `*`(2, 3))
7
(1 + (2 * 3))
7
- Compare with lisp:
(+ 1 0 (* 2 3 1 1 1))
7
7.4 How could you make this call easier to read?
mean(, TRUE, x = c(1:10, NA))
5.5
mean(x = c(1:10, NA), na.rm=TRUE)
5.5
7.5 Does the following function throw an error when called? Why/why not?
f2 <- function(a, b) { a * 10 } f2(10, stop("This is an error!"))
100
- Lazy evaluation (b is not used).
7.6 What is an infix function? How do you write it? What’s a replacement function? How do you write it?
- The function name comes in between its arguments. `%+%` <- function
- Replacement functions act like they modify their arguments in place, and have the special name xxx<-.
`xxx<-` <- function
7.7 What function do you use to ensure that a cleanup action occurs regardless of how a function terminates?
- on.exit()