vfunc packageTo cite the vfunc package in publications please use
R Core Team (2024).
In mathematics, given two functions \(f,g\colon\mathbb{R}\longrightarrow\mathbb{R}\),
it is natural to define \(f+g\) as the
function that maps \(x\in\mathbb{R}\)
to \(f(x) + g(x)\). However, in base R,
objects of class function do not have arithmetic methods
defined, so idiom such as f + g returns an error, even
though it has a perfectly reasonable expectation. The
vfunc package offers this functionality. Other similar
features are provided, which lead to compact and readable idiom. A wide
class of coding bugs is eliminated.
Consider the following R session:
## Error in f + g: non-numeric argument to binary operator
Above, there is a reasonably clear expectation for
f + g: it should give a function that returns the sum of
f() and g(); something like
function(x){f(x) + g(x)}. However, it returns an error
because f and g are objects of S4
class function, which do not have an addition method.
Further, it is not possible to define Arith group
S4 methods [in this case, overloading addition] so that
this idiom operates as desired. This is because the
function class is sealed in S4: the
definition of new methods for it is prohibited. Here I present the
vfunc R package that furnishes appropriate idiom. The
package defines a new S4 class vf (“virtual
function”) which inherits from function, but for which new
methods can be defined. This device furnishes some ways to apply
Arith methods for functions.
The package is designed so that objects of class vf
operate as functions but are subject to arithmetic operations, which are
executed transparently. For example:
## [1] Inf 3.00000 8.50000 15.66667 24.75000 35.80000 48.83333 63.85714
## [9] 80.87500 99.88889
Above, we coerce f and g to objects of
S4 class vf [for “virtual function”]. Such
objects have Arith methods defined and may be combined
arithmetically; for example addition is dispatched to
function(e1, e2){as.vf(function(...){e1(...) + e2(...)})}
The vf class has a single .Data slot of
type function which means that objects of this class
inherit much of the behaviour of base class function;
above, we see that e1 and e2 may be executed
with their argument list directly. In practice this means that
f+g behaves as intended, and suggests other ways in which
it can be used:
## [1] NaN 4.00000 11.50000 20.00000 30.25000 42.40000 56.50000
## [8] 72.57143 90.62500 110.66667
The advantages of such idiom fall in to two main categories. Firstly,
code can become considerably more compact; and secondly one can guard
against a wide class of hard-to-find bugs. Now consider f()
and g() to be trivariate functions, each taking three
arguments, say,
and \(x=1.2\), \(y=1.7\), \(z=4.3\). Given this, we wish to calculate
\[(f(x,y,z) + g(x,y,z))(f(x,y,z) + 4 - 2f(x,y,z)g(x,y,z)).\]
How would one code up such an expression in R? The standard way would be
## [1] 2.411975
Note the repeated specification of argument list
(x,y,z), repeated here five times. Now use the
vfunc package:
## [1] 2.411975
See how the package allows one to ‘’factorize’’ the argument list so it appears once, leading to more compact code. It is also arguably less error-prone, as the following example illustrates. Consider
\[ f(x+z,y+z,f(x,x,y)-g(x,x,y)) + g(x+z, y+z,f(x,x,y)-g(x,x,y)) \]
(such expressions arise in the study of dynamical systems). Note that functions \(f\) and \(g\) are to be evaluated with two distinct sets of arguments at different levels of nesting, namely \((x,x,y)\) at the inner level and \((x+z,y+z,f(x,x,y)-g(x,x,y)\) at the outer. Standard R idiom would be
## [1] 64.04918
The author can attest that finding bugs in such expressions can be
difficult [it is easy to mistype (x,x,y) in one of its
occurrences, yet difficult to detect the error]. However,
vfunc idiom would be
## [1] 64.04918
which is certainly shorter, arguably neater and at least the author
finds such constructions considerably less error-prone. In this form,
one can be sure that both f() and g() are
called with identical arguments at each of the two levels in the
expression, as the arguments appear only once.
Looking again at the method for vf addition, viz
function(e1, e2){as.vf(function(...){e1(...) + e2(...)})}
we see the + operator is used to sum the return values
of e1() and e2(). There is no reason that this
operator cannot itself be overloaded, and the vfunc package
works transparently if this is the case, with either S3 or
S4. Taking the onion package (Hankin
2006) as an example:
library("onion")
options("show_onions_compactly" = TRUE)
f <- as.vf(function(x,y){x + x*y})
g <- as.vf(function(x,y){x^2 + y})
(f + g - f*g)(1 + Hj,Hk)## Re
## 4+2i+2j-1k
The R language includes a number of primitive functions as
S4 Math generics, including the trig functions such as
sin(), and a few others such as the cumulative sum
cumsum(). These functions are quite deep-seated and cannot
easily be modified to work with objects of class vf. The
package defines capitalized versions of primitive functions to operate
with other objects of class vf. Taking sin()
as an example we have
## An object of class "vf"
## function (x)
## {
## sin(x)
## }
## <bytecode: 0x56005577d918>
## <environment: namespace:vfunc>
Then we may, for example, combine trig functions with user-defined functions:
## [1] 0.9769132
Above, we see package idiom being used to evaluate \(\sin^2(0.32) + 3 + \sin(0.32^2+2) - 3\cdot\sin 0.32\cdot(0.32^2+2)\). In base R:
## [1] 0.9769132
This construction allows one to define composite functions such as
j <- as.vf(function(x,y){Cos(x) + Sin(x-y)})
k <- as.vf(function(x,y){Tan(x) + Log(x+y)})
l <- as.vf(function(x,y){Sin(x/2) + x^2 })(note that functions j(), k() and
l() are bivariate). Then compare
## [1] 2.545235
with the one-stage idiom which reads:
j(sin(sin(0.4) + tan(0.4)) + log(sin(0.4) + tan(0.4)), cos(sin(0.4) + tan(0.4)) +
exp(sin(0.4) + tan(0.4))) + k(sin(sin(0.4) + tan(0.4)) + log(sin(0.4) + tan(0.4)),
cos(sin(0.4) + tan(0.4)) + exp(sin(0.4) + tan(0.4)))+ l(sin(sin(0.4) + tan(0.4)) +
log(sin(0.4) + tan(0.4)), cos(sin(0.4) + tan(0.4)) + exp(sin(0.4) + tan(0.4)))## [1] 2.545235
and the multi-stage idiom:
A <- function(x,y){j(x,y) + k(x,y) + l(x,y)}
B <- function(x){sin(x) + log(x)}
C <- function(x){cos(x) + exp(x)}
D <- function(x){sin(x) + tan(x)}
x <- 0.4
A(B(D(x)), C(D(x)))## [1] 2.545235
See how the one-stage idiom is very long, and the multi-stage idiom
is opaque [and nevertheless has repeated instances of (x,y)
and x].
The vfunc package allows functions to be ‘’factorized’’,
that is, f(x) + g(x) to be re-written
(f + g)(x). This allows for concise idiom and eliminates a
certain class of coding errors. The package also allows for recursive
application of such ideas.