attempt

所属分类:开发工具
开发工具:R
文件大小:0KB
下载次数:0
上传日期:2022-12-14 20:33:25
上 传 者sh-1993
说明:  R中用于防御编程的工具,
(Tools for defensive programming in R,)

文件列表:
.Rbuildignore (208, 2022-12-14)
.travis.yml (171, 2022-12-14)
CONDUCT.md (1387, 2022-12-14)
DESCRIPTION (845, 2022-12-14)
LICENSE (39, 2022-12-14)
NAMESPACE (850, 2022-12-14)
NEWS.md (911, 2022-12-14)
R/ (0, 2022-12-14)
R/adverbs.R (4518, 2022-12-14)
R/attempt-package.R (518, 2022-12-14)
R/if.R (2223, 2022-12-14)
R/is_class.R (250, 2022-12-14)
R/on_error.R (657, 2022-12-14)
R/try_catch.R (2767, 2022-12-14)
R/utils.R (343, 2022-12-14)
R/warn_if.R (6825, 2022-12-14)
appveyor.yml (860, 2022-12-14)
attempt.Rproj (386, 2022-12-14)
codecov.yml (176, 2022-12-14)
cran-comments.md (529, 2022-12-14)
data-raw/ (0, 2022-12-14)
data-raw/devstuffs.R (1018, 2022-12-14)
docs/ (0, 2022-12-14)
docs/CONDUCT.html (6066, 2022-12-14)
docs/LICENSE-text.html (4471, 2022-12-14)
docs/LICENSE.html (3647, 2022-12-14)
docs/articles/ (0, 2022-12-14)
docs/articles/a_intro_attempt.html (11698, 2022-12-14)
docs/articles/b_try_catch.html (31497, 2022-12-14)
docs/articles/c_adverbs.html (13049, 2022-12-14)
docs/articles/d_if.html (10255, 2022-12-14)
docs/articles/e_conditions.html (21556, 2022-12-14)
docs/articles/index.html (4943, 2022-12-14)
docs/articles/tests_and_coverage.html (16089, 2022-12-14)
... ...

[![Travis build status](https://travis-ci.org/ColinFay/attempt.svg?branch=master)](https://travis-ci.org/ColinFay/attempt) [![Coverage status](https://codecov.io/gh/ColinFay/attempt/branch/master/graph/badge.svg)](https://codecov.io/github/ColinFay/attempt?branch=master) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/ColinFay/attempt?branch=master&svg=true)](https://ci.appveyor.com/project/ColinFay/attempt) [![](https://cranlogs.r-pkg.org/badges/attempt)](https://CRAN.R-project.org/package=attempt) # {attempt} ![](https://raw.githubusercontent.com/ColinFay/attempt/master/README-fig/attempt_hex_200.png) Tools for defensive programming, inspired by `{purrr}` mappers and based on `{rlang}`. `{attempt}` is designed to handle the cases when something / someone attempts to do something it shouldn’t. For example: - an attempt to run a `log("a")` (error) - an attempt to connect to a web API without an internet connexion (error) - an attempt to `paste()` `"good morning"` and `iris` (message/warning) - … `{attempt}` provides several condition handlers, from try catch to simple message printing. `{attempt}` only depends on `{rlang}`, and every function is design to be fast, making it easy to implement in other functions and packages. # Install From CRAN: ``` r install.packages("attempt") ``` The dev version: ``` r install.packages("attempt", repo = "https://colinfay.me/ran") ``` # Reference ``` r library(attempt) ``` ## attempt `attempt()` is a wrapper around base `try()` that allows you to insert a custom messsage on error. ``` r attempt(log("a")) # Error: non-numeric argument to mathematical function attempt(log("a"), msg = "Nop !") # Error: Nop ! ``` You can make it verbose (i.e.returning the expression): ``` r attempt(log("a"), msg = "Nop !", verbose = TRUE) # Error in log("a"): Nop ! ``` Of course the result is returned if there is one: ``` r attempt(log(1), msg = "Nop !", verbose = TRUE) # [1] 0 ``` As with `try()`, the result can be saved as an error object: ``` r a <- attempt(log("a"), msg = "Nop !", verbose = TRUE) a # [1] "Error in log(\"a\"): Nop !\n" # attr(,"class") # [1] "try-error" # attr(,"condition") # ``` You can check if the result is an error with `is_try_error()` ``` r a <- attempt(log("a"), msg = "Nop !", verbose = FALSE) #> Error: Nop ! is_try_error(a) #> [1] TRUE ``` ## silent\_attempt `silent_attempt()` is a wrapper around `silently()` (see further down for more info) and `attempt()`. It attempts to run the expr, stays silent if the expression succeeds, and returns error or warnings if any. ``` r silent_attempt(log("a")) # Error: non-numeric argument to mathematical function silent_attempt(log(1)) ``` ## try catch You can write a try catch with these params: - `expr` the expression to be evaluated - `.e` a mapper or a function evaluated when an error occurs - `.w` a mapper or a function evaluated when a warning occurs - `.f` a mapper or an expression which is always evaluated before returning or exiting In `.e` and `.f`, the `.x` refers to the error / warning object. ### With mappers ``` r try_catch(expr = log("a"), .e = ~ paste0("There is an error: ", .x), .w = ~ paste0("This is a warning: ", .x)) #[1] "There is an error: Error in log(\"a\"): non-numeric argument to mathematical function\n" try_catch(log("a"), .e = ~ stop(.x), .w = ~ warning(.x)) # Error in log("a") : non-numeric argument to mathematical function try_catch(matrix(1:3, nrow= 2), .e = ~ print(.x), .w = ~ print(.x)) # try_catch(expr = 2 + 2 , .f = ~ print("Using R for addition... ok I'm out!")) # [1] "Using R for addition... ok I'm out!" # [1] 4 ``` As usual, the handlers are set only if you call them: ``` r try_catch(matrix(1:3, nrow = 2), .e = ~ print("error")) # [,1] [,2] # [1,] 1 3 # [2,] 2 1 # Warning message: # In matrix(1:3, nrow = 2) : # data length [3] is not a sub-multiple or multiple of the number of rows [2] ``` ``` r try_catch(matrix(1:3, nrow = 2), .w = ~ print("warning")) # [1] "warning" ``` ### Traditionnal way `{attempt}` is flexible in how you can specify your arguments. You can, as you do with `{base}` `tryCatch()`, use a plain old function: ``` r try_catch(log("a"), .e = function(e){ print(paste0("There is an error: ", e)) print("Ok, let's save this") time <- Sys.time() a <- paste("+ At",time, ", \nError:",e) # write(a, "log.txt", append = TRUE) # commented to prevent log.txt creation print(paste("log saved on log.txt at", time)) print("let's move on now") }) # [1] "There is an error: Error in log(\"a\"): non-numeric argument to mathematical function\n" # [1] "Ok, let's save this" # [1] "log saved on log.txt at 2018-01-30 16:59:13" # [1] "let's move on now" ``` You can even mix both: ``` r try_catch(log("a"), .e = function(e){ paste0("There is an error: ", e) }, .f = ~ print("I'm not sure you can do that pal !")) # [1] "I'm not sure you can do that pal !" # [1] "There is an error: Error in log(\"a\"): non-numeric argument to mathematical function\n" try_catch(log("a"), .e = ~ paste0("There is an error: ", .x), .f = function() print("I'm not sure you can do that pal !")) # [1] "I'm not sure you can do that pal !" # [1] "There is an error: Error in log(\"a\"): non-numeric argument to mathematical function\n" ``` ### `try_catch_df()` `try_catch_df()` returns a tibble with the call, the error message if any, the warning message if any, and the value of the evaluated expression or “error”. The values will always be contained in a list-column. ``` r res_log <- try_catch_df(log("a")) res_log #> call error warning value #> 1 log("a") non-numeric argument to mathematical function NA error res_log$value #> [[1]] #> [1] "error" res_matrix <- try_catch_df(matrix(1:3, nrow = 2)) res_matrix #> call error #> 1 matrix(1:3, nrow = 2) NA #> warning #> 1 data length [3] is not a sub-multiple or multiple of the number of rows [2] #> value #> 1 1, 2, 3, 1 res_matrix$value #> [[1]] #> [,1] [,2] #> [1,] 1 3 #> [2,] 2 1 res_success <- try_catch_df(log(1)) res_success #> call error warning value #> 1 log(1) NA NA 0 res_success$value #> [[1]] #> [1] 0 ``` ### `map_try_catch()` `map_try_catch()` and `map_try_catch_df()` allow you to map on a list of arguments `l`, to be evaluated by the function in `fun`. ``` r map_try_catch(l = list(1, 3, "a"), fun = log, .e = ~ .x) #> [[1]] #> [1] 0 #> #> [[2]] #> [1] 1.098612 #> #> [[3]] #> map_try_catch_df(list(1,3,"a"), log) #> call error #> 1 .Primitive("log")(1) #> 2 .Primitive("log")(3) #> 3 .Primitive("log")("a") non-numeric argument to mathematical function #> warning value #> 1 NA 0 #> 2 NA 1.098612 #> 3 NA error ``` ## Adverbs Adverbs take a function and return a modified function. ### `silently()` `silently()` transforms a function so that when you call this new function, it returns nothing unless there is an error or a warning (contrary to `attempt` that returns the result). In a sense, the new function stay silent unless error or warning. ``` r silent_log <- silently(log) silent_log(1) silent_log("a") #> Error in .f(...) : non-numeric argument to mathematical function # Error in .f(...) : non-numeric argument to mathematical function ``` With `silently()`, the result is never returned. ``` r silent_matrix <- silently(matrix) silent_matrix(1:3, 2) #Warning message: #In .f(...) : # data length [3] is not a sub-multiple or multiple of the number of rows [2] ``` ### `surely()` `surely()` transforms a function so that when you call this new function, it calls `attempt()` - i.e.in the code below, calling `sure_log(1)` is the same as calling `attempt(log(1))`. In a sense, you’re sure this new function will always work. ``` r sure_log <- surely(log) sure_log(1) # [1] 0 sure_log("a") # Error: non-numeric argument to mathematical function ``` ### `with_message()` and `with_warning()` These two functions take a function, and add a warning or a message to it. ``` r as_num_msg <- with_message(as.numeric, msg = "We're performing a numeric conversion") as_num_warn <- with_warning(as.numeric, msg = "We're performing a numeric conversion") as_num_msg("1") #> We're performing a numeric conversion #> [1] 1 as_num_warn("1") #> Warning in as_num_warn("1"): We're performing a numeric conversion #> [1] 1 ``` ### `without_message()`, `without_warning()`, and `discretly()` These three functions do the opposite, as they remove warnings and messages: ``` r matrix(1:3, ncol = 2) no_warning_matrix <- without_warning(matrix) no_warning_matrix(1:3, ncol = 2) ``` ## `if_` conditions `if_none()`, `if_any()` and `if_all()` test the elements of the list. ``` r if_all(1:10, ~ .x < 11, ~ return(letters[1:10])) #> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" if_any(1:10, is.numeric, ~ "Yay!") #> [1] "Yay!" if_none(1:10, is.character, ~ rnorm(10)) #> [1] -0.62041260 1.18903502 0.34425236 0.40211829 -0.89651209 #> [6] 0.08673175 -0.65066136 0.47734033 -1.34301785 -0.20193553 ``` The defaut for all `.p` is `isTRUE()`. So you can: ``` r a <- c(FALSE, TRUE, TRUE, TRUE) if_any(a, .f = ~ "nop!") #> [1] "nop!" ``` `if_then()` performs a simple “if this then do that”: ``` r if_then(1, is.numeric, ~ "nop!") #> [1] "nop!" ``` `if_not()` runs `.f` if `.p(.x)` is not TRUE : ``` r if_not(.x = 1, .p = is.character, ~ ".x is not a character") #> [1] ".x is not a character" ``` And `if_else()` is a wrapper around `base::ifelse()`. If you want these function to return a value, you need to wrap these values into a mapper / a function. E.g, to return a vector, you’ll need to write `if_then(1, is.numeric, ~ "Yay")`. ``` r a <- if_else(1, is.numeric, ~ "Yay", ~ "Nay") a #> [1] "Yay" ``` ## warnings and messages The `stop_if()`, `warn_if()` and `message_if()` are easy to use functions that send an error, a warning or a message if a condition is met. Each function has its counterpart with `_not` that returns a message if the condition is not met. `stop_if_not()` is quite the same as `assert_that()` from the {assertthat} package, except that it can takes mappers. It is not the same as base `stopifnot()`, as it doesn’t take a list of expression. These functions are also flexible as you can pass base predicates (is.numeric, is.character…), a custom predicate built with mappers, or even your own predicate function. You can either choose a custom message or just let the built-in messages be printed: ``` r x <- 12 # Stop if .x is numeric stop_if(.x = x, .p = is.numeric) #> Error: Test `is.numeric` on `x` returned an error. y <- "20" # stop if .x is not numeric stop_if_not(.x = y, .p = is.numeric, msg = "y should be numeric") #> Error: y should be numeric a <- "this is not numeric" # Warn if .x is charcter warn_if(.x = a, .p = is.character) #> Warning: Test `is.character` on `a` returned a warning. b <- 20 # Warn if .x is not equal to 10 warn_if_not(.x = b, .p = ~ .x == 10 , msg = "b should be 10") #> Warning: b should be 10 c <- "a" # Message if c is a character message_if(.x = c, .p = is.character, msg = "You entered a character element") #> You entered a character element # Build more complex predicates d <- 100 message_if(.x = d, .p = ~ sqrt(.x) < 42, msg = "The square root of your element must be more than 42") #> The square root of your element must be more than 42 # Or, if you're kind of old school, you can still pass classic functions e <- 30 message_if(.x = e, .p = function(vec){ return(sqrt(vec) < 42) }, msg = "The square root of your element must be more than 42") #> The square root of your element must be more than 42 ``` If you need to call a function that takes no argument at `.p` (like `curl::has_internet()`), use this function as `.x`. ``` r stop_if(.x = curl::has_internet(), msg = "You shouldn't have internet to do that") #> Error: You shouldn't have internet to do that warn_if(.x = curl::has_internet(), msg = "You shouldn't have internet to do that") #> Warning: You shouldn't have internet to do that message_if(.x = curl::has_internet(), msg = "Huray, you have internet \\o/") #> Huray, you have internet \o/ ``` If you don’t specify a `.p`, the default test is `isTRUE()`. ``` r a <- is.na(airquality$Ozone) message_if_any(a, msg = "NA found") #> NA found ``` ### In function That can come really handy inside a function: ``` r my_fun <- function(x){ stop_if_not(.x = curl::has_internet(), msg = "You should have internet to do that") warn_if_not(x, is.character, msg = "x is not a character vector. The output may not be what you're expecting.") paste(x, "is the value.") } my_fun(head(iris)) #> Warning: x is not a character vector. The output may not be what you're #> expecting. #> [1] "c(5.1, 4.9, 4.7, 4.6, 5, 5.4) is the value." #> [2] "c(3.5, 3, 3.2, 3.1, 3.6, 3.9) is the value." #> [3] "c(1.4, 1.4, 1.3, 1.5, 1.4, 1.7) is the value." #> [4] "c(0.2, 0.2, 0.2, 0.2, 0.2, 0.4) is the value." #> [5] "c(1, 1, 1, 1, 1, 1) is the value." ``` ### none, all, any `stop_if()`, `warn_if()` and `message_if()` all have complementary tests with `_all`, `_any` and `_none`, which combine the `if_*` and the `warn_*`, `stop_*` and `message_*` seen before. They take a list as first argument, and a predicate. They test if any, all or none of the elements validate the predicate. ``` r stop_if_any(iris, is.factor, msg = "Factors here. This might be due to stringsAsFactors.") #> Error: Factors here. This might be due to stringsAsFactors. warn_if_none(1:10, ~ .x < 0, msg = "You need to have at least one number under zero.") #> Warning: You need to have at least one number under zero. message_if_all(1:100, is.numeric, msg = "That makes a lot of numbers.") #> That makes a lot of numbers. ``` ## `on_error()` `on_error()` behaves as `on.exit()` except it happens only when there is an error in the function. ``` r y <- function(x){ on_error(~ print("ouch")) log(x) } y(12) [1] 2.484907 y("a") Error in log(x) : non-numeric argument to mathematical function [1] "ouch" ``` # Misc ## Acknowledgments Thanks to [Romain](http://romain.rbind.io/) for the name suggestion. ## Contact Questions and feedbacks [welcome](mailto:contact@colinfay.me)\! You want to contribute ? Open a [PR](https://github.com/ColinFay/attempt/pulls) :) If you encounter a bug or want to suggest an enhancement, please [open an issue](https://github.com/ColinFay/attempt/issues). Please note that this project is released with a [Contributor Code of Conduct](CONDUCT.md). By participating in this project you agree to abide by its terms.

近期下载者

相关文件


收藏者