{"action":"created","issue":{"url":"https://api.github.com/repos/hadley/purrr/issues/275","repository_url":"https://api.github.com/repos/hadley/purrr","labels_url":"https://api.github.com/repos/hadley/purrr/issues/275/labels{/name}","comments_url":"https://api.github.com/repos/hadley/purrr/issues/275/comments","events_url":"https://api.github.com/repos/hadley/purrr/issues/275/events","html_url":"https://github.com/hadley/purrr/issues/275","id":195177581,"number":275,"title":"strictly()","user":{"login":"egnha","id":12226767,"avatar_url":"https://avatars.githubusercontent.com/u/12226767?v=3","gravatar_id":"","url":"https://api.github.com/users/egnha","html_url":"https://github.com/egnha","followers_url":"https://api.github.com/users/egnha/followers","following_url":"https://api.github.com/users/egnha/following{/other_user}","gists_url":"https://api.github.com/users/egnha/gists{/gist_id}","starred_url":"https://api.github.com/users/egnha/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/egnha/subscriptions","organizations_url":"https://api.github.com/users/egnha/orgs","repos_url":"https://api.github.com/users/egnha/repos","events_url":"https://api.github.com/users/egnha/events{/privacy}","received_events_url":"https://api.github.com/users/egnha/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":6,"created_at":"2016-12-13T07:03:42Z","updated_at":"2016-12-14T08:24:00Z","closed_at":"2016-12-14T07:32:28Z","body":"A common form of boilerplate code at the top of functions is argument checking: You make some checks on the arguments, signal a condition if any show-stopping checks fail, then move on to the meat of the function if everything is good. The problem with this approach is that it can clutter up the main work of a function with admin; it spoils the \"fun\" of a function with the inconvenience of a security check.\r\n\r\nA function `strictly()`, as a cohort of `safely()` and `possibly()`, would separate these concerns, functionally. It could have the signature\r\n```r\r\nstrictly(.f, ..., cond = NULL)\r\n```\r\nwhere `.f` is an interpreted function and `cond` is a condition object (if not `NULL`). The main part is `...`, which consists of two-sided formulas `p ~ m` that specify the argument checks for `.f`:\r\n\r\n* `p` is an expression that is a call to a predicate function (the \"check\")\r\n* `m` is an expression whose evaluation can be coerced to a string (the \"failure message\" if the check fails)\r\n\r\nThe value of `strictly(.f, ...)` would be a function that applies `.f` \"strictly.\"\r\n\r\nExample:\r\n```r\r\nfoo <- function(f, x) x - f(x)\r\n\r\nis_trig <- function(f) any(map_lgl(c(cos, sin, tan), identical, y = f))\r\nchk_trig <- is_trig(f) ~ \"f not trigonometric\"\r\nfoo_s <- strictly(foo, is.numeric(x) ~ \"x not numeric\", chk_trig)\r\n\r\nfoo_s(sin, 2)    # [1] 1.090703\r\nfoo_s(log, 2)    # Error in foo_s(log, 2) : f not trigonometric\r\nfoo_s(cos, \"a\")  # Error in foo_s(cos, \"a\") : x not numeric\r\nfoo_s(log, \"a\")  # Error in foo_s(log, \"a\") : f not trigonometric; x not numeric\r\n```\r\n(While the error messages could be more informative, the basic utility is clear.)\r\n\r\nHere's a basic implementation, with a modicum of metaprogramming:\r\n```r\r\nstrictly <- function(.f, ..., cond = NULL) {\r\n  ..cond <- cond %||% identity\r\n  ..chks <- list(...)\r\n\r\n  .is_fml <- map_lgl(..chks, is_formula)\r\n  if (!(is_function(.f) && all(.is_fml))) {\r\n    stop(\"Invalid arguments for strictly()\", call. = FALSE)\r\n  }\r\n\r\n  # Use two-sided `..` to make clashes with names in body(.f) improbable\r\n  .body <- substitute({\r\n    ..env.. <- as.list(environment())\r\n    ..is_pass.. <- map_lgl(chks, lazyeval::f_eval_lhs, data = ..env..)\r\n    if (!all(..is_pass..)) {\r\n      ..msg.. <- chks[!..is_pass..] %>%\r\n        map_chr(~ as.character(lazyeval::f_eval_rhs(.x, ..env..))) %>%\r\n        paste(collapse = \"; \")\r\n      stop(..cond(..msg..))\r\n    }\r\n    body\r\n  }, list(body = body(.f), chks = ..chks))\r\n\r\n  .f_strict <- eval(call(\"function\", formals(.f), as.call(.body)))\r\n  environment(.f_strict) <- list2env(as.list(environment(.f), all.names = TRUE),\r\n                                     parent = parent.env(environment(.f)))\r\n  environment(.f_strict)$..cond <- ..cond\r\n  environment(.f_strict)$..chks <- ..chks\r\n\r\n  .f_strict\r\n}\r\n```\r\n\r\nAlternatively, one could implement `strictly()` as a function-composition. However, the above implementation has the advantage of preserving the argument signature of `.f`, as well as its source code and environment.\r\n\r\nWould `strictly()` be a meaningful addition to purrr?"},"comment":{"url":"https://api.github.com/repos/hadley/purrr/issues/comments/266972321","html_url":"https://github.com/hadley/purrr/issues/275#issuecomment-266972321","issue_url":"https://api.github.com/repos/hadley/purrr/issues/275","id":266972321,"user":{"login":"egnha","id":12226767,"avatar_url":"https://avatars.githubusercontent.com/u/12226767?v=3","gravatar_id":"","url":"https://api.github.com/users/egnha","html_url":"https://github.com/egnha","followers_url":"https://api.github.com/users/egnha/followers","following_url":"https://api.github.com/users/egnha/following{/other_user}","gists_url":"https://api.github.com/users/egnha/gists{/gist_id}","starred_url":"https://api.github.com/users/egnha/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/egnha/subscriptions","organizations_url":"https://api.github.com/users/egnha/orgs","repos_url":"https://api.github.com/users/egnha/repos","events_url":"https://api.github.com/users/egnha/events{/privacy}","received_events_url":"https://api.github.com/users/egnha/received_events","type":"User","site_admin":false},"created_at":"2016-12-14T08:24:00Z","updated_at":"2016-12-14T08:24:00Z","body":"@hadley I guess I need to do my homework! Didn't know about these packages, either. Thanks.\r\n\r\nBoth [Types for R](https://github.com/jimhester/types), and the related package [Declarative function argument check](https://github.com/gaborcsardi/argufy#readme), tackle the larger problem. The second one especially is quite close in implementation to what I've suggested, but uses roxygen comments rather than formals to specify checks. Nonetheless, relying on a comments-based mechanism is not as flexible as using formals, à la lazyeval.\r\n\r\n`strictly` has no pretension of solving argument checking once and for all, but rather to provide a simple functional version of it to cover common use cases, without much fuss. I thought it fit in harmoniously with `safely` and `possibly`, as providing a kind of \"dual\" functionality to these (checking/acting on the input vs. the output).\r\n\r\nBut you are right. The basic problem is big and nontrivial, and I do see the rationale behind leaving `strictly`—or something analogous—out of purrr. (Even a \"poor man's\" version of it.)"}}
{"actor":{"display_login":"egnha"}}