I make use of the assertthat
package quite often to check postconditions in functions. When reading more about the idea of Design by Contract I stumbled upon the idea to make checks of output in comparison to input values.
The most simple example is the following:
toggle <- function(x)!x
One can immediately state that x == !old_x
must always be true. (old_x
stands for the value of x
before evaluation.)
(Of course this example is oversimplified and the postcondition does not add more useful information for humans or computers. A more useful example is on the bottom of the question..)
So I can extend my toggle
function as follows to check that condition with every call:
toggle <- function(x){
old_x <- x
x <- !x
assertthat::assert_that(x == !old_x)
return(x)
}
This works of course but I wondered if there's another way to access the value of old_x
without explicitely store it (or the result) under a new name. And without splitting the postcondition-checking code to the top and bottom of the function. Something along the line of how R evaluates function calls..
One attempt I can think of is to use sys.call
and eval.parent
to access to the old values:
toggle <- function(x){
x <- !x
.x <- eval.parent(as.list(sys.call())[[2]])
assertthat::assert_that(x == !.x)
return(x)
}
This works, but I still need to assign a new variable .x
and also the subsetting with [[2]]
is not flexible yet. However writing it like assertthat::assert_that(x == !eval.parent(as.list(sys.call())[[2]])
does not work and playing around with the search levels of sys.call(-1 ..)
did not help.
Another (a bit more useful) example where the postcondition adds some information:
increment_if_smaller_than_2 <- function(x){
old_x <- x
x <- ifelse(x < 2, x <- x + 1, x)
assertthat::assert_that(all(x >= old_x))
return(x)
}
Any hints?
You can access the old parameter-values by accessing it via the parent environment. For this solution to work, you need to introduce new variable(s) for the return-result, i.e. retval, to prevent re-assignments to method-params. IMHO this isn't a serious drawback, since it's good programming-style not to overwrite method-parameters anyway. You could i.e. do the following:
test <- function(.a) {
retval <- 2 * .a
assertthat::assert_that(abs(retval) >= abs(.a))
return(retval)
}
a <- 42
test(a)
# [1] 84
If you would like to take it a step further and submit the assertion-function dynamically you could do that as follows:
test_with_assertion <- function(.a, assertion) {
retval <- 2 * .a
assertthat::assert_that(assertion(retval, eval.parent(.a)))
return(retval)
}
a <- 42
test_with_assertion(a, function(new_value, old_value)
abs(new_value) >= abs(eval.parent(old_value)) )
# [1] 84
Does this do, what you intended to do?