在应用“按合同设计”时,存储输入值以检查后置条件是否成立

我经常使用assertthat包来检查函数中的后置条件.当阅读更多关于契约式设计的想法时,我偶然发现了与输入值相比较的输出检查的想法.

最简单的例子如下:

toggle <- function(x)!x

可以立即声明x ==!old_x必须始终为真. (old_x代表评估前的x值.)

(当然这个例子过于简单,后置条件不会为人类或计算机增加更多有用的信息.一个更有用的例子是问题的底部..)

所以我可以按如下方式扩展我的切换功能,以便在每次调用时检查该条件:

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)
}

任何提示?

最佳答案 您可以通过父环境访问旧参数值来访问它们.要使此解决方案起作用,您需要为返回结果(即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

这样做,你打算做什么?

点赞