The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
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.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.