Giter Club home page Giter Club logo

ellipsis's Introduction

ellipsis

Lifecycle: maturing CRAN status Travis build status Codecov test coverage

Adding ... to a function is a powerful technique because it allows you to accept any number of additional arguments. Unfortunately it comes with a big downside: any misspelled or extraneous arguments will be silently ignored. This package provides tools for making ... safer:

  • check_dots_used() errors if any components of ... are not evaluated. This allows an S3 generic to state that it expects every input to be evaluated.

  • check_dots_unnamed() errors if any components of ... are named. This allows you to collect arbitrary unnamed arguments, warning if the user misspells a named argument.

  • check_dots_empty() errors if ... is used. This allows you to use ... to force the user to supply full argument names, while still warning if an argument name is misspelled.

Thanks to Jenny Bryan for the idea, and Lionel Henry for the heart of the implementation.

Installation

Install the released version from CRAN:

install.packages("ellipsis")

Or the development version from GitHub:

devtools::install_github("r-lib/ellipsis")

Example

mean() is a little dangerous because you might expect it to work like sum():

sum(1, 2, 3, 4)
#> [1] 10
mean(1, 2, 3, 4)
#> [1] 1

This silently returns the incorrect result because mean() has arguments x and .... The ... silently swallows up the additional arguments. We can use ellipsis::check_dots_used() to check that every input to ... is actually used:

safe_mean <- function(x, ..., trim = 0, na.rm = FALSE) {
  ellipsis::check_dots_used()
  mean(x, ..., trim = trim, na.rm = na.rm)
}

safe_mean(1, 2, 3, 4)
#> Error: 3 components of `...` were not used.
#> 
#> We detected these problematic arguments:
#> * `..1`
#> * `..2`
#> * `..3`
#> 
#> Did you misspecify an argument?

ellipsis's People

Contributors

batpigandme avatar dkahle avatar hadley avatar jimhester avatar jyuu avatar krlmlr avatar lionel- avatar noamross avatar yutannihilation avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ellipsis's Issues

Documentation inheritance

For instance, @inheritParams ellipsis::dots_empty would generate:

These dots are for future extensions and must be empty.

This way we don't end up with multiple variations of the above.

Bug, I think

library(ordinal)
#> Warning: package 'ordinal' was built under R version 3.5.1

fit <- clm(rating ~ temp * contact, data = wine)

broom_confint_terms <- function(x, ...) {
  
  # warn on arguments silently being ignored
  ellipsis::check_dots_used()
  ci <- confint(x, ...)
  
  # confint called on models with a single predictor
  # often returns a named vector rather than a matrix :(
  
  if (is.null(dim(ci))) {
    ci <- matrix(ci, nrow = 1)
    rownames(ci) <- names(coef(x))[1]
  }
  
  ci <- as_tibble(ci, rownames = "term")
  names(ci) <- c("term", "conf.low", "conf.high")
  ci
}

conf.level <- 0.99
broom_confint_terms(fit, level = conf.level)
#> Error in eval_bare(exit_handler, env): object 'ellipsis_eval_bare' not found

confint(fit, level = 0.99)
#>                          0.5 %   99.5 %
#> tempwarm             0.5919548 4.248065
#> contactyes          -0.3059129 3.147587
#> tempwarm:contactyes -2.0408370 2.761428

Created on 2019-03-10 by the reprex package (v0.2.1)

Release ellipsis 0.1.1

Needed for vctrs

Prepare for release:

  • devtools::check()
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • revdepcheck::revdep_check(num_workers = 4)

Submit to CRAN:

  • usethis::use_version('minor')
  • Update cran-comments.md
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted ๐ŸŽ‰
  • usethis::use_github_release()
  • usethis::use_dev_version()

Make on.exit explicit?

Should the UI be on.exit(check_dots_unused())? I think hiding the on.exit() makes it harder to read. Also harder to experiment with. Might also lead to bugs by writing things like:

check_dots_unused()

on.exit(cleanup())

Release ellipsis 0.1.0

Prepare for release:

  • devtools::check()
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • revdepcheck::revdep_check(num_workers = 4)
  • Polish NEWS

Submit to CRAN:

  • usethis::use_version()
  • Update cran-comments.md
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted ๐ŸŽ‰
  • usethis::use_github_release()
  • usethis::use_dev_version()

False positive when a formal is matched in a method but not used

IMHO this should not cause a warning, as a was matched by the method, just not used in the body.

library(ellipsis)

foo <- function(x, ...) {
  ellipsis::check_dots_used()
  UseMethod("foo")
}

foo.character <- function(x, ..., a) {
  x
}

foo("hi", a = 1)
#> Warning: Some components of ... were not used: a
#> [1] "hi"

Created on 2018-07-09 by the reprex package (v0.2.0).

False negative if method calls a different generic also using `check_dots_used`

I would expect the call to bar() to issue a warning here, because the bar generic is passed an argument a without it being used by the method. Also note you get a warning if from the foo check_dots_used() if you do not explicitly force the promise for a.

library(ellipsis)

foo <- function(x, ...) {
  ellipsis::check_dots_used()
  UseMethod("foo")
}

bar <- function(x, ...) {
  ellipsis::check_dots_used()
  UseMethod("bar")
}

foo.character <- function(x, ..., a = 1) {
  force(a)
  bar(x, ..., a = a)
}

bar.character <- function(x, ...) {
  x
}

foo("hi", a = 1)
#> [1] "hi"

Created on 2018-07-09 by the reprex package (v0.2.0).

Lazy warnings?

If ellipsis is used in low level tools, warning immediately might result in an avalanche of output when the tools are used in loops.

Release ellipsis 0.0.2

Prepare for release:

  • devtools::check()
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • rhub::check(platform = 'ubuntu-rchk')
  • rhub::check_with_sanitizers()

Submit to CRAN:

  • usethis::use_version('0.1.0')
  • devtools::check_win_devel() (again!)
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • usethis::use_github_release()
  • usethis::use_dev_version()

Move `master` branch to `main`

The master branch of this repository will soon be renamed to main, as part of a coordinated change across several GitHub organizations (including, but not limited to: tidyverse, r-lib, tidymodels, and sol-eng). We anticipate this will happen by the end of September 2021.

That will be preceded by a release of the usethis package, which will gain some functionality around detecting and adapting to a renamed default branch. There will also be a blog post at the time of this master --> main change.

The purpose of this issue is to:

  • Help us firm up the list of targetted repositories
  • Make sure all maintainers are aware of what's coming
  • Give us an issue to close when the job is done
  • Give us a place to put advice for collaborators re: how to adapt

message id: euphoric_snowdog

Clarify the doc about quosures

Hi,

As stated in the Tidyverse design guide, you cannot use check_dots_used if your function uses enquos(...) or match.call() somewhere.

I think it would be a good thing to add something in the documentation of check_dots_used that clarifies this point. You also might want to state whether this is a permanent limitation or a limitation that could be fixed someday.

Thanks for this useful package

Include function name in error

f <- function(x, ...) {
  ellipsis::check_dots_used(action = rlang::warn)
}
f(z = 1)
#> Warning: 1 components of `...` were not used.
#> 
#> We detected these problematic arguments:
#> * `z`
#> 
#> Did you misspecify an argument?

Can we make it this?

#> Warning: 1 components of `f(...)` were not used.

Probably also worth a refresh here to use our more modern style:

#> Warning: Some arguments in `f(...)` were not used.
#> x Unused arguments: `z`
#> i Did you misspecify an argument?

False positive inside generic

as_factor <- function(x, ...) {
  ellipsis::check_dots_used()
  UseMethod("as_factor")
}

as_factor.character <- function(x, ..., y)  {
  force(y)
  x
}
as_factor("x", y = 1)
#> Warning: Some components of ... were not used: y
#> [1] "x"

Created on 2019-02-18 by the reprex package (v0.2.1.9000)

check_dots_empty()?

Since check_dots_used() checks that dots were used, I would expect check_dots_unused() to check the opposite, that dots were passed but not used. check_dots_empty() seems like a better name.

Release ellipsis 0.3.0

Prepare for release:

  • devtools::check()
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • rhub::check(platform = 'ubuntu-rchk')
  • rhub::check_with_sanitizers()
  • revdepcheck::revdep_check(num_workers = 4)
  • Polish NEWS
  • Polish pkgdown reference index
  • Draft blog post

Submit to CRAN:

  • usethis::use_version('minor')
  • Update cran-comments.md
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted ๐ŸŽ‰
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • Finish blog post
  • Tweet
  • Add link to blog post in pkgdown news menu

check_dots_empty()

For the case where you're using ... simply to force the user to fully name details arguments

CRAN errors on recent R-devel

This is intended as a heads-up before many upcoming errors in CRAN checks.

Starting from R-devel version "2020-05-09 r78394", CRAN checks produce errors with the same message "'where' must be an environment" (for example, see this log). This comes from base::bquote(...), which is used in ellipsis:::exit_handler(action).

Current CRAN version of 'ellipsis' has implementation of exit_handler() using bquote() with where argument being a list. Recent update in R-devel explicitly prohibited where to be anything other than environment (see this commit on R-devel). This seems to be the root cause of errors.

on_exit() implementation in development version is different and is using eval() (changed in this commit), which might have already fixed the issue, but is not yet on CRAN.

cc @hadley, @lionel- , as this seems to be important ('ellipsis' is used in 'tidyr', which has many reverse dependencies).

Method to call downstream function with appropriate ... args?

Due respect, I'm not sure if this is the best place to ask the question.

Scenario:

  • Parent function passes ... to one or more downstream functions.
  • Downstream function does not allow ..., and has a lot of arguments (83).
  • Parent function would like to allow ... for customization of downstream function.
  • Parent function would like to pass only appropriate ... arguments to downstream function, to avoid causing an error.

From section 6.2.4 https://adv-r.hadley.nz/functions.html#function-fundamentals it appears that do.call(function, args) could be used for this purpose. I wonder if some type of function would live in this package?

Example scenario:

my_lil_function <- function(x, ...) {
   mean_function(x, ...)
}
mean_function <- function(x, na.rm=FALSE) {
   mean(x, na.rm=na.rm)
}
# success
my_lil_function(c(1, 2.5, 3, NA), na.rm=TRUE)
# error
my_lil_function(c(1, 2.5, 3, NA), na.rm=TRUE, color="red")
# Error in mean_function(x, ...) : unused argument (color = "red")

Rough idea of workaround:

# new function for this package?
call_fn_ellipsis <- function(FUN, ...) {
   FUN_argnames <- names(formals(FUN));
   if ("..." %in% FUN_argnames) {
      # if FUN allows ... then pass all of ...
      FUN(...)
   } else {
      # if FUN does not allow ... then pass only what it accepts
      arglist <- list(...)
      argkeep <- which(names(arglist) %in% FUN_argnames)
      arguse <- arglist[argkeep]
      do.call(FUN, arguse)
   }
}

# new method would involve using do.call()
my_new_lil_function <- function(x, ...) {
   args_to_send <- c(list(x=x),
      list(...));
   do.call(call_fn_ellipsis,
      c(FUN=mean_function, args_to_send))
}
# now success
my_new_lil_function(c(1, 2.5, 3, NA), na.rm=TRUE, color="red")
# [1] 2.17

Main drivers:

  • I don't want to add 83 arguments to my_lil_function() in order to avoid using ...
  • I want to use ... for customizations to two different functions called by my_lil_function()
  • I do not control the downstream function, so I can't add ... to that function's arguments.

Suggestions, feedback, guidance welcomed!
Maybe there is an R package function that already performs this step.
Thank you!

Strange behaviour of check_dots_empty() in S4 objects returning a tibble

I have an S4 class defined as part of a Bioconductor package and some of the slots are used to store tibble objects. I've just made the upgrade to R4.0.2 (from R3.6.3) and am now getting an unexpected warning when retrieving these slots. Strangely this isn't always given as a warning upon the initial call, but is returned every time a resultant object is printed. For an MWE:

options(warn = 2)
library(tibble)
setClass("track", slots = c(x="numeric", y="data.frame"))
myTrack <- new("track", x = -4:4, y = tibble(y = 1))
myTrack
df <- slot(myTrack, "y")
df
traceback()
16: doWithOneRestart(return(expr), restart)
15: withOneRestart(expr, restarts[[1L]])
14: withRestarts({
        .Internal(.signalCondition(cond, message, call))
        .Internal(.dfltWarn(message, call))
    }, muffleWarning = function() NULL)
13: warning(cnd)
12: action(message, .subclass = c(.subclass, "rlib_error_dots"), 
        ...)
11: action_dots(action = action, message = "`...` is not empty.", 
        dot_names = names(dots), note = "These dots only exist to allow future extensions and should be empty.", 
        .subclass = "rlib_error_dots_nonempty")
10: check_dots_empty(action = warn)
9: pillar::colonnade(df, has_row_id = if (star) "*" else TRUE, needs_dots = needs_dots)
8: shrink_mat(df, rows, n, star = has_rownames(x))
7: trunc_mat(x, n = n, width = width, n_extra = n_extra)
6: format.tbl(x, ..., n = n, width = width, n_extra = n_extra)
5: format(x, ..., n = n, width = width, n_extra = n_extra)
4: paste0(..., collapse = "\n")
3: cli::cat_line(format(x, ..., n = n, width = width, n_extra = n_extra))
2: print.tbl(x)
1: (function (x, ...) 
   UseMethod("print"))(x)

Interestingly, the package passes R CMD check despite these warnings being produced by most examples.

R version 4.0.2 (2020-06-22)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_AU.UTF-8       LC_NUMERIC=C               LC_TIME=en_AU.UTF-8       
 [4] LC_COLLATE=en_AU.UTF-8     LC_MONETARY=en_AU.UTF-8    LC_MESSAGES=en_AU.UTF-8   
 [7] LC_PAPER=en_AU.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_AU.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] tibble_3.0.2

loaded via a namespace (and not attached):
 [1] compiler_4.0.2   assertthat_0.2.1 magrittr_1.5     ellipsis_0.3.1   cli_2.0.2        tools_4.0.2     
 [7] pillar_1.4.5     glue_1.4.1       rstudioapi_0.11  crayon_1.3.4     utf8_1.1.4       fansi_0.4.1     
[13] vctrs_0.3.1      lifecycle_0.2.0  pkgconfig_2.0.3  rlang_0.4.7  

Thanks in advance

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.