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?

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)
  1. Which base function has the most arguments?
  2. How many base functions have no arguments? What’s special about those functions?
  3. 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()

Author: Andreas Karlsson

Created: 2017-02-23 tor 15:28

Validate