tidyverse / purrr

A functional programming toolkit for R
https://purrr.tidyverse.org/
Other
1.27k stars 272 forks source link

Update formals of partial #690

Closed flying-sheep closed 4 years ago

flying-sheep commented 5 years ago

Autocompletion for partial(f, arg1 = 1)(<tab> just results in ... =, instead of listing f’s signature.

The formals of the partialized function should be updated.

lionel- commented 4 years ago

This would be hard to achieve given the current implementation.

lionel- commented 4 years ago

Reopening as I think we can fix this problem by abandoning support for a corner case (dots coming from multiple environments).

lionel- commented 4 years ago

Actually we can't solve this without breaking support for NSE functions. I have added an explanation in the documentation:

#' `partial()` creates a function that takes `...` arguments. Unlike
#' [compose()] and other function operators like [negate()], it
#' doesn't reuse the function signature of `.f`. This is because
#' `partial()` explicitly supports NSE functions that use
#' `substitute()` on their arguments. The only way to support those is
#' to forward arguments through dots.
flying-sheep commented 4 years ago

Actually I have some experience with this. It is possible to modify ... without evaluating it. pryr::dots() is just:

dots <- function(...) {
  eval(substitute(alist(...)))
}

that way we can tinker with the unevaluated dots and then pass them on. Or am I missing something?

lionel- commented 4 years ago

The problem is that partial(plot, 1:3) with updated formals creates this function (simplified for the example):

function(y) plot(1:3, y)

Then when plot() uses substitute() on its argument, it correctly finds 1:3, but incorrectly finds y.

This is why partial() uses dots, so that substitute() (and tidyeval equivalents) work out of the box:

function(...) plot(1:3, ...)

To my knowledge there is no other way to fix this. You might try to create an NSE function that defuses and forwards arguments, but then you'll run into issues of incorrect evaluation environments, unless you use quosures. This will add overhead to the function created by partial(), and we've just removed all the overhead.

It is possible to modify ... without evaluating it. pryr::dots() is

I recommend using quosures for capturing dots. Since dots may be forwarded across different levels of functions, the parent.frame() is not the correct evaluation environment in the general case.

flying-sheep commented 4 years ago

OK, I see. Thank you for the explanation!