存储输入值以检查在应用按合同设计时后置条件是否成立
Storing input value to check wether postcondition holds true when applying Design-by-Contract
我经常使用 assertthat
包来检查函数中的后置条件。在阅读更多有关按合同设计的想法时,我偶然发现了将输出与输入值进行比较检查的想法。
最简单的例子如下:
toggle <- function(x)!x
可以立即声明 x == !old_x
必须始终为真。 (old_x
代表x
求值前的值。)
(当然这个例子过于简单了,后置条件并没有为人类或计算机添加更多有用的信息。一个更有用的例子在问题的底部..)
所以我可以按如下方式扩展我的 toggle
函数以在每次调用时检查该条件:
toggle <- function(x){
old_x <- x
x <- !x
assertthat::assert_that(x == !old_x)
return(x)
}
这当然有效,但我想知道是否有另一种方法可以访问 old_x
的值,而无需将其(或结果)显式存储在新名称下。并且没有将后置条件检查代码拆分到函数的顶部和底部。与 R 如何评估函数调用有关的内容..
我能想到的一种尝试是使用 sys.call
和 eval.parent
来访问旧值:
toggle <- function(x){
x <- !x
.x <- eval.parent(as.list(sys.call())[[2]])
assertthat::assert_that(x == !.x)
return(x)
}
这可行,但我仍然需要分配一个新变量 .x
,而且 [[2]]
的子集还不灵活。然而,像 assertthat::assert_that(x == !eval.parent(as.list(sys.call())[[2]])
这样写是行不通的,玩弄 sys.call(-1 ..)
的搜索级别也无济于事。
另一个(更有用的)示例,其中后置条件添加了一些信息:
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)
}
有什么提示吗?
您可以通过父环境访问旧参数值。要使此解决方案起作用,您需要为 return-结果引入新变量,即 retval,以防止重新分配给方法参数。恕我直言,这不是一个严重的缺点,因为无论如何都不覆盖方法参数是一种很好的编程风格。您可以即执行以下操作:
test <- function(.a) {
retval <- 2 * .a
assertthat::assert_that(abs(retval) >= abs(.a))
return(retval)
}
a <- 42
test(a)
# [1] 84
如果您想更进一步并动态提交断言函数,您可以按如下方式进行:
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
这样做,你打算做什么?
我经常使用 assertthat
包来检查函数中的后置条件。在阅读更多有关按合同设计的想法时,我偶然发现了将输出与输入值进行比较检查的想法。
最简单的例子如下:
toggle <- function(x)!x
可以立即声明 x == !old_x
必须始终为真。 (old_x
代表x
求值前的值。)
(当然这个例子过于简单了,后置条件并没有为人类或计算机添加更多有用的信息。一个更有用的例子在问题的底部..)
所以我可以按如下方式扩展我的 toggle
函数以在每次调用时检查该条件:
toggle <- function(x){
old_x <- x
x <- !x
assertthat::assert_that(x == !old_x)
return(x)
}
这当然有效,但我想知道是否有另一种方法可以访问 old_x
的值,而无需将其(或结果)显式存储在新名称下。并且没有将后置条件检查代码拆分到函数的顶部和底部。与 R 如何评估函数调用有关的内容..
我能想到的一种尝试是使用 sys.call
和 eval.parent
来访问旧值:
toggle <- function(x){
x <- !x
.x <- eval.parent(as.list(sys.call())[[2]])
assertthat::assert_that(x == !.x)
return(x)
}
这可行,但我仍然需要分配一个新变量 .x
,而且 [[2]]
的子集还不灵活。然而,像 assertthat::assert_that(x == !eval.parent(as.list(sys.call())[[2]])
这样写是行不通的,玩弄 sys.call(-1 ..)
的搜索级别也无济于事。
另一个(更有用的)示例,其中后置条件添加了一些信息:
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)
}
有什么提示吗?
您可以通过父环境访问旧参数值。要使此解决方案起作用,您需要为 return-结果引入新变量,即 retval,以防止重新分配给方法参数。恕我直言,这不是一个严重的缺点,因为无论如何都不覆盖方法参数是一种很好的编程风格。您可以即执行以下操作:
test <- function(.a) {
retval <- 2 * .a
assertthat::assert_that(abs(retval) >= abs(.a))
return(retval)
}
a <- 42
test(a)
# [1] 84
如果您想更进一步并动态提交断言函数,您可以按如下方式进行:
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
这样做,你打算做什么?