Skip to content

refactor: use checkmate for arg validation #286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 29, 2024
Merged

refactor: use checkmate for arg validation #286

merged 3 commits into from
Jan 29, 2024

Conversation

dshemetov
Copy link
Contributor

@dshemetov dshemetov commented Jan 26, 2024

Related to cmu-delphi/epiprocess#403.

I took the safe approach of changing the internals of the arg functions to use checkmate, that way we can rely on the existing unit tests and don't need to change any other epipredict code. The functions look a lot simpler and more unified now. We could eventually get rid of these wrappers and just invoke check functions directly, but that would take more refactoring work.

@dshemetov dshemetov requested a review from dajmcdon as a code owner January 26, 2024 23:52
@@ -44,7 +44,7 @@ test_that("scalar", {
test_that("numeric", {
expect_silent(arg_is_numeric(i, j, x, y))
expect_error(arg_is_numeric(a))
expect_error(arg_is_numeric(d))
expect_silent(arg_is_numeric(d))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dates now pass arg_is_numeric, this didn't affect the rest of the tests.

@@ -56,7 +56,7 @@ test_that("numeric", {
test_that("positive", {
expect_silent(arg_is_pos(i, j, x, y))
expect_error(arg_is_pos(a))
expect_error(arg_is_pos(d))
expect_silent(arg_is_pos(d))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dates now pass arg_is_positive, this didn't affect the rest of the tests.

@@ -68,7 +68,7 @@ test_that("positive", {
test_that("nonneg", {
expect_silent(arg_is_nonneg(i, j, x, y))
expect_error(arg_is_nonneg(a))
expect_error(arg_is_nonneg(d))
expect_silent(arg_is_nonneg(d))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dates now pass arg_is_nonneg, this didn't affect the rest of the tests.

@@ -96,7 +96,8 @@ test_that("date", {
expect_error(arg_is_date(d, dd, n))
expect_error(arg_is_date(d, dd, nn))
expect_silent(arg_is_date(d, dd, n, allow_null = TRUE))
expect_silent(arg_is_date(d, dd, nn, allow_na = TRUE))
# Upstream issue, see: https://github.com/mllg/checkmate/issues/256
# expect_silent(arg_is_date(d, dd, nn, allow_na = TRUE))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to remove allow_na from arg_is_date, due to a checkmate inconsistency, but this didn't affect the rest of the tests.

expect_silent(arg_is_sorted(b = NULL, allow_null = TRUE))
})


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These weren't used anywhere and checkmate supports a sorted check for some data types, so I figured we could rely on that when we need it.

Copy link
Contributor

@dajmcdon dajmcdon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these changes result in reasonable/interpretable error messages (still get the argument names correct in the error message, etc.) Can you maybe show an example in the PR comments?

@dshemetov
Copy link
Contributor Author

Good question right, I brought back the handle_arg_list since it handled grabbing names well. Checkmate does quite well with preserving variable names in errors.

r$> r1 <- epi_recipe(x) %>%
          step_epi_ahead(death_rate, ahead = 3.6) %>%
          step_epi_lag(death_rate, lag = 1.9)
Error in (function (name, value)  : 
  Assertion on 'ahead' failed: Must be of type 'integerish', but element 1 is not close to an integer.

r$> r2 <- epi_recipe(x) %>%
          step_epi_ahead(death_rate, ahead = 7) %>%
          step_epi_lag(death_rate, lag = -7)
Error in (function (name, value)  : 
  Assertion on 'lag' failed: Element 1 is not >= 0.

r$> step_growth_rate(r, value, role = 1)
Error in (function (name, value)  : 
  Assertion on 'role' failed: Must be of type 'character', not 'double'.

r$> step_growth_rate(r, value, horizon = 0)
Error in (function (name, value)  : 
  Assertion on 'horizon' failed: Element 1 is not >= 1.

Copy link
Contributor

@dajmcdon dajmcdon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps for a future Issue: it would be nice if those error messages more specifically described the calling function in which the error occurred.

@dshemetov
Copy link
Contributor Author

FWIW, traceback() can lead the user there

r$> r1 <- epi_recipe(x) %>%
              step_epi_ahead(death_rate, ahead = 3.6) %>%
              step_epi_lag(death_rate, lag = 1.9)
Error in (function (name, value)  : 
  Assertion on 'ahead' failed: Must be of type 'integerish', but element 1 is not close to an integer.

r$> traceback()
15: stop(simpleError(sprintf(msg, ...), call.))
14: mstop("Assertion on '%s' failed: %s.", var.name, res, call. = sys.call(-2L))
13: makeAssertion(x, res, .var.name, add)
12: assert_integerish(value, null.ok = allow_null, lower = 0, any.missing = FALSE, 
        .var.name = name) at utils-arg.R#63
11: (function (name, value) 
    {
        assert_integerish(value, null.ok = allow_null, lower = 0, 
            any.missing = FALSE, .var.name = name)
    })(dots[[1L]][[1L]], dots[[2L]][[1L]])
10: mapply(.f, .x, .y, MoreArgs = list(...), SIMPLIFY = FALSE) at compat-purrr.R#72
9: map2(.x, .y, .f, ...) at compat-purrr.R#15
8: walk2(names, values, .tests) at utils-arg.R#10
7: handle_arg_list(..., .tests = function(name, value) {
       assert_integerish(value, null.ok = allow_null, lower = 0, 
           any.missing = FALSE, .var.name = name)
   }) at utils-arg.R#62
6: arg_is_nonneg_int(ahead) at step_epi_shift.R#126
5: step_epi_ahead(., death_rate, ahead = 3.6)
4: inherits(x, "epi_recipe") at epi_recipe.R#221
3: is_epi_recipe(recipe) at step_epi_shift.R#64
2: step_epi_lag(., death_rate, lag = 1.9)
1: epi_recipe(x) %>% step_epi_ahead(death_rate, ahead = 3.6) %>% 
       step_epi_lag(death_rate, lag = 1.9)

@dajmcdon
Copy link
Contributor

True, though it would be nice if Error in (function (name, value) : said Error in step_epi_ahead() : instead.

@dshemetov dshemetov merged commit 3a00268 into dev Jan 29, 2024
@dshemetov dshemetov deleted the ds/checks branch January 29, 2024 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants