Chapter 8 Apendix A4 – rogramming tidbits
8.1 The Ellipsis
= function(x, polynomial = 2){
bar return(x^polynomial)
}
= function(x,...){
foo = bar(x,...)
z return(z)
}
foo(6)
## [1] 36
foo(6, polynomial = 3)
## [1] 216
foo(6, polynomial = 2)
## [1] 36
8.2 match.call()
The R documentation states “match.call retunrs a call in which all of the specified arguments are specified by their full names.”
Cool. What?
What this does is takes in a function, in our case the lm
function and returns an object which is of class "call"
. This disassembles the specified function from its input arguments, allowing us to manipulate arguments without evaluating them.
Suppose we have a function
= function(x,y){
foo = match.call()
bar = match(c('x', 'y'), names(bar))
names = list(bar, names)
args return(args)
}
foo(2,4)
## [[1]]
## foo(x = 2, y = 4)
##
## [[2]]
## [1] 2 3
We see the output of match.call
is effective a regurgitation of our input. What match.call
has done is disassemble our function without evaluating it so we can index into our arguments directly. match
then allows us to explicitly define the arguments of a function as new variables without evaluating our function.
Agan, this all seems relatively etherial and unnecessary. So, let’s look at a practical example of why this is useful. Say we wanted to write our own function that takes in a user specified formula and outputs a design matrix data frame using the built in stats::model.frame
function.
= function(formula, data, weights){
foo = stats::model.frame(formula = formula, data = data, weights = weights)
model
}foo(y~x, data = data.frame(y = rnorm(10), x = runif(10)))
## Error in model.frame.default(formula = formula, data = data, weights = weights): invalid type (closure) for variable '(weights)'
You see we get an error here. The reason being that we didn’t specify a default value for our weights input. As a result, R tries to use a built in function stats::weights
to fill in the missing weights
argument. For a small function like this it would be trivial to add in weights = NULL
as a default argument in the inner function, but for large functions with dozens of arguments, this is tedious and unnecessary.
We could circumvent this by using the match.call
argument.
= function(formula, data, weights){
foo = match.call() #freeze the function and regurgitate it as a call class
mf = match(c('formula', 'data', 'weights'), names(mf),0L) #extract arguments that are relevant for our lower level function.
m = mf[c(1L, m)]
mf $drop.unused.levels = TRUE #gets rid of unused arguments
mf= quote(stats::model.frame)#replace 'foo' with 'stats::modelframe'
mf[[1L]]
mf
}
foo(y~x, data = data.frame(y = rnorm(10), x = runif(10)))
## stats::model.frame(formula = y ~ x, data = data.frame(y = rnorm(10),
## x = runif(10)), drop.unused.levels = TRUE)
So you see our output is now a respecified form of our orginal function. However, also note it has not yet been evaluated. We can add one more line of code to unfreeze time and evaluate the expression.
= function(formula, data, weights){
foo = match.call() #freeze the function and regurgitate it as a call class
mf = match(c('formula', 'data', 'weights'), names(mf),0L) #extract arguments that are relevant for our lower level function.
m = mf[c(1L, m)]
mf $drop.unused.levels = TRUE #gets rid of unused arguments
mf= quote(stats::model.frame)#replace 'foo' with 'stats::modelframe'
mf[[1L]] eval(mf)
}
foo(y~x, data = data.frame(y = rnorm(10), x = runif(10)))
## y x
## 1 0.7077870 0.5346722
## 2 0.5259239 0.8335249
## 3 0.6047109 0.9842342
## 4 0.2054106 0.8383571
## 5 1.2202408 0.4338031
## 6 0.8241745 0.4732692
## 7 -0.7060031 0.8553911
## 8 0.6882589 0.6423501
## 9 1.0299911 0.4453169
## 10 0.5221993 0.8782426
As you can see, we have effectively redefined our function without evaluating it. Again, this can be completely ciorcumvented by specifying default arguments, but often this is an unreasonable task for large projects.
You can return from your side quest here
P.S Another benefit of this method is it allows you to specify arguments in your function that have the same name as .Primitive
s. All-in-all its just another defensive programming strategy that can also be leveraged to convert between functions within other functions.