Description Usage Arguments Examples
Try to find a small and efficient representation of the
concatenation of to functions f
and g
such that the resulting
function has the form h(...)=g(...,f(...),...)
(where ...
stands
for an arbitrary numer of arguments, not the R
-style dots).
More precisely: The input are two functions f
and g
and a
"bridge argument" f2g
. Both functions may have an arbirary argument
list, say f(x,y,z=7,c)
and g(a=3,b=4,c)
. The "bridge argument"
can be any parameter of g
, let's say b
. The result of
function.compose(f, g, f2g="b")
will then be a function
h(a=3,c,x,y,z=7)
which is equivalent to g(a, f(x, y, z, c), c)
.
Since we also canonicalize all functions (f
, g
, and h
)
involved, this means also that we attempt to substitute all potentially
substitutable variables and simplify the inner expressions as much as
possible.
This is particularly useful when iteratively composing functions and wanting to retain their readability and making the result as fast to execute as possible. However, the behavior may be undefined if the parameters of the function can be resolved as constant values in the current environment or if functions with otherwise odd parameters are successfively composed.
If the result of f
is used exactly once in g
, we substitute
the body of f
into g
to directly replace the bridge parameter.
If it is used more than once, we first shovel its result into a variable,
namely the bridge parameter.
As example, assume you have set k <- 23
and now want to compose the
functions f<-function(x) { (k*x) + 7 }
and g<-function(x) {
(k*k-x*x) / (x - sin(k)) }
to a function h
. You can do that by
writing h<-function(x) g(f(x))
. Of course, if you later try to inspect
h
and just write h
, you will see exactly this,
function(x) g(f(x))
. This leads to two issues: First, if you do not
know f
and g
, the output is meaningless and opaque, you cannot
interpret it. Second, evaluating h
is unnecessarily slow: It performs
two inner function calls and needs to evaluate a variable k
at several
locations, although the value of k
should be fixed to 23
.
Matter of fact, also k*k
and sin(k)
are constants which could
be known.
The goal of function.compose
is to resolve these two issues. If you do
h<-function.compose(f, g)
instead of h<-function(x) g(f(x))
, a
new function composed of both the bodies of f
and g
is created.
Furthermore, as many of the variables and expressions in the body which can
be resolved as possible are replaced by their constant result. Printing the
result of h<-function.compose(f, g)
would yield (something like)
function (x) { x <- (23 * x) + 7; (529 - x * x)/(x -
-0.846220404175171) }
.
Additionally, we replace identical sub-expressions in the composed function by the same object. This may be advantegous for a slightly faster execution due to being more cache friendly.
1 | function.compose(f, g, f2g = "x")
|
f |
the inner function |
g |
the outer function |
f2g |
the argument of |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | f<-function(x,y,z=7,c) { x+23*y-z+2+c }
g<-function(a=3,b=4,c) { a*b - b*c }
function.compose(f, g, f2g="b")
# function (a = 3, c, x, y, z = 7)
# {
# b <- x + 23 * y - z + 2 + c
# a * b - b * c
# }
function.compose(sin, cos, f2g="x")
# function (x)
# cos(x = sin(x = x))
k <- 23
f2 <- function(x) { k*5 + x }
g2 <- function(x) { x/(k - sin(k)) }
function.compose(f2, g2)
# function (x)
# (115 + x)/23.8462204041752
k<-23
f<-function(x) { (k*x) + 7 }
g<-function(x) { (k*k-x*x) / (x - sin(k)) }
h.plain<-function(x) g(f(x))
h.plain
# function(x) g(f(x))
h.composed<-function.compose(f, g)
h.composed
# function (x)
# {
# x <- (23 * x) + 7
# (529 - x * x)/(x - -0.846220404175171)
# }
i<-45
j<-33
k<-23
f <- function(x) { (x*(x-i)) - x/sinh(k*cos(j-atan(k+j))) }
g <- function(x) { abs(x)^(abs(1/(3-i))) + (j - k*exp(-i)) / ((i*j) * x) }
h.1.plain <- function(x) g(f(x))
h.1.plain
# function(x) g(f(x))
h.1.composed <- function.compose(f, g)
h.1.composed
# function (x)
# {
# x <- (x * (x - 45)) - x/4818399372.40284
# abs(x)^0.0238095238095238 + 33/(1485 * x)
# }
h.2.plain <- function(x) g(f(g(f(x))))
h.2.plain
# function(x) g(f(g(f(x))))
h.2.composed <- function.compose(function.compose(function.compose(f, g), f), g)
h.2.composed
# function (x)
# {
# x <- {
# x <- {
# x <- (x * (x - 45)) - x/4818399372.40284
# abs(x)^0.0238095238095238 + 33/(1485 * x)
# }
# (x * (x - 45)) - x/4818399372.40284
# }
# abs(x)^0.0238095238095238 + 33/(1485 * x)
# }
x <- runif(1000)
library(microbenchmark)
microbenchmark(h.1.plain(x), h.1.composed(x), h.2.plain(x), h.2.composed(x))
# Unit: microseconds
# expr min lq mean median uq max neval
# h.1.plain(x) 78.841 79.4880 83.05224 79.9775 85.8485 119.824 100
# h.1.composed(x) 75.890 76.4675 93.23504 76.8385 78.9615 896.681 100
# h.2.plain(x) 153.793 154.8100 166.31210 155.5855 164.3685 743.360 100
# h.2.composed(x) 149.035 149.4870 155.25070 149.8895 154.0960 213.395 100
|
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.