Сохранение входного значения для проверки того, будет ли выполнено посткондиционное условие при применении Design-by-Contract - PullRequest
0 голосов
/ 17 мая 2018

Я часто использую пакет 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)
}

Есть подсказки?

1 Ответ

0 голосов
/ 23 мая 2018

Вы можете получить доступ к старым значениям параметров, обратившись к нему через родительскую среду.Чтобы это решение работало, вам нужно ввести новую переменную (и) для возвращаемого результата, то есть 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

это делать, что вы намеревались сделать?

...