On 03/05/2018 11:01 AM, William Dunlap via R-devel wrote:> In R-3.5.0 you can use ...length(): > > f <- function(..., n) ...length() > > f(stop("one"), stop("two"), stop("three"), n=7) > [1] 3 > > Prior to that substitute() is the way to go > > g <- function(..., n) length(substitute(...())) > > g(stop("one"), stop("two"), stop("three"), n=7) > [1] 3 > > R-3.5.0 also has the ...elt(n) function, which returns > the evaluated n'th entry in ... , without evaluating the > other ... entries. > > fn <- function(..., n) ...elt(n) > > fn(stop("one"), 3*5, stop("three"), n=2) > [1] 15 > > Prior to 3.5.0, eval the appropriate component of the output > of substitute() in the appropriate environment: > > gn <- function(..., n) { > + nthExpr <- substitute(...())[[n]] > + eval(nthExpr, envir=parent.frame()) > + } > > gn(stop("one"), environment(), stop("two"), n=2) > <environment: R_GlobalEnv> >Bill, the last of these doesn't quite work, because ... can be passed down through a string of callers. You don't necessarily want to evaluate it in the parent.frame(). For example: x <- "global" f <- function(...) { x <- "f" g(...) } g <- function(...) { firstExpr <- substitute(...())[[1]] c(list(...)[[1]], eval(firstExpr, envir = parent.frame())) } Calling g(x) correctly prints "global" twice, but calling f(x) incorrectly prints [1] "global" "f" You can get the first element of ... without evaluating the rest using ..1, but I don't know a way to do this for general n in pre-3.5.0 base R. Duncan Murdoch
On Thu, May 3, 2018 at 8:18 AM, Duncan Murdoch <murdoch.duncan at gmail.com> wrote:> On 03/05/2018 11:01 AM, William Dunlap via R-devel wrote: >> >> In R-3.5.0 you can use ...length(): >> > f <- function(..., n) ...length() >> > f(stop("one"), stop("two"), stop("three"), n=7) >> [1] 3 >> >> Prior to that substitute() is the way to go >> > g <- function(..., n) length(substitute(...())) >> > g(stop("one"), stop("two"), stop("three"), n=7) >> [1] 3 >> >> R-3.5.0 also has the ...elt(n) function, which returns >> the evaluated n'th entry in ... , without evaluating the >> other ... entries. >> > fn <- function(..., n) ...elt(n) >> > fn(stop("one"), 3*5, stop("three"), n=2) >> [1] 15 >> >> Prior to 3.5.0, eval the appropriate component of the output >> of substitute() in the appropriate environment: >> > gn <- function(..., n) { >> + nthExpr <- substitute(...())[[n]] >> + eval(nthExpr, envir=parent.frame()) >> + } >> > gn(stop("one"), environment(), stop("two"), n=2) >> <environment: R_GlobalEnv> >> > > Bill, the last of these doesn't quite work, because ... can be passed down > through a string of callers. You don't necessarily want to evaluate it in > the parent.frame(). For example: > > x <- "global" > f <- function(...) { > x <- "f" > g(...) > } > g <- function(...) { > firstExpr <- substitute(...())[[1]] > c(list(...)[[1]], eval(firstExpr, envir = parent.frame())) > } > > Calling g(x) correctly prints "global" twice, but calling f(x) incorrectly > prints > > [1] "global" "f" > > You can get the first element of ... without evaluating the rest using ..1, > but I don't know a way to do this for general n in pre-3.5.0 base R.If you don't mind using a package: # works with R 3.1 and up library(rlang) x <- "global" f <- function(...) { x <- "f" g(...) } g <- function(...) { dots <- enquos(...) eval_tidy(dots[[1]]) } f(x, stop("!")) #> [1] "global" g(x, stop("!")) #> [1] "global" Hadley -- http://hadley.nz
Hi, It would be great if one of the experts could comment on the difference between Hadley's dotlength and ...length? The fact that someone bothered to implement a new primitive for that when there seems to be a very simple and straightforward R-only solution suggests that there might be some gotchas/pitfalls with the R-only solution. Thanks, H. On 05/03/2018 08:34 AM, Hadley Wickham wrote:> On Thu, May 3, 2018 at 8:18 AM, Duncan Murdoch <murdoch.duncan at gmail.com> wrote: >> On 03/05/2018 11:01 AM, William Dunlap via R-devel wrote: >>> >>> In R-3.5.0 you can use ...length(): >>> > f <- function(..., n) ...length() >>> > f(stop("one"), stop("two"), stop("three"), n=7) >>> [1] 3 >>> >>> Prior to that substitute() is the way to go >>> > g <- function(..., n) length(substitute(...())) >>> > g(stop("one"), stop("two"), stop("three"), n=7) >>> [1] 3 >>> >>> R-3.5.0 also has the ...elt(n) function, which returns >>> the evaluated n'th entry in ... , without evaluating the >>> other ... entries. >>> > fn <- function(..., n) ...elt(n) >>> > fn(stop("one"), 3*5, stop("three"), n=2) >>> [1] 15 >>> >>> Prior to 3.5.0, eval the appropriate component of the output >>> of substitute() in the appropriate environment: >>> > gn <- function(..., n) { >>> + nthExpr <- substitute(...())[[n]] >>> + eval(nthExpr, envir=parent.frame()) >>> + } >>> > gn(stop("one"), environment(), stop("two"), n=2) >>> <environment: R_GlobalEnv> >>> >> >> Bill, the last of these doesn't quite work, because ... can be passed down >> through a string of callers. You don't necessarily want to evaluate it in >> the parent.frame(). For example: >> >> x <- "global" >> f <- function(...) { >> x <- "f" >> g(...) >> } >> g <- function(...) { >> firstExpr <- substitute(...())[[1]] >> c(list(...)[[1]], eval(firstExpr, envir = parent.frame())) >> } >> >> Calling g(x) correctly prints "global" twice, but calling f(x) incorrectly >> prints >> >> [1] "global" "f" >> >> You can get the first element of ... without evaluating the rest using ..1, >> but I don't know a way to do this for general n in pre-3.5.0 base R. > > If you don't mind using a package: > > # works with R 3.1 and up > library(rlang) > > x <- "global" > f <- function(...) { > x <- "f" > g(...) > } > g <- function(...) { > dots <- enquos(...) > eval_tidy(dots[[1]]) > } > > f(x, stop("!")) > #> [1] "global" > g(x, stop("!")) > #> [1] "global" > > Hadley >-- Herv? Pag?s Program in Computational Biology Division of Public Health Sciences Fred Hutchinson Cancer Research Center 1100 Fairview Ave. N, M1-B514 P.O. Box 19024 Seattle, WA 98109-1024 E-mail: hpages at fredhutch.org Phone: (206) 667-5791 Fax: (206) 667-1319
On 03/05/2018 11:18 AM, Duncan Murdoch wrote:> On 03/05/2018 11:01 AM, William Dunlap via R-devel wrote: >> In R-3.5.0 you can use ...length(): >> > f <- function(..., n) ...length() >> > f(stop("one"), stop("two"), stop("three"), n=7) >> [1] 3 >> >> Prior to that substitute() is the way to go >> > g <- function(..., n) length(substitute(...())) >> > g(stop("one"), stop("two"), stop("three"), n=7) >> [1] 3 >> >> R-3.5.0 also has the ...elt(n) function, which returns >> the evaluated n'th entry in ... , without evaluating the >> other ... entries. >> > fn <- function(..., n) ...elt(n) >> > fn(stop("one"), 3*5, stop("three"), n=2) >> [1] 15 >> >> Prior to 3.5.0, eval the appropriate component of the output >> of substitute() in the appropriate environment: >> > gn <- function(..., n) { >> + nthExpr <- substitute(...())[[n]] >> + eval(nthExpr, envir=parent.frame()) >> + } >> > gn(stop("one"), environment(), stop("two"), n=2) >> <environment: R_GlobalEnv> >> > > Bill, the last of these doesn't quite work, because ... can be passed > down through a string of callers. You don't necessarily want to > evaluate it in the parent.frame(). For example: > > x <- "global" > f <- function(...) { > x <- "f" > g(...) > } > g <- function(...) { > firstExpr <- substitute(...())[[1]] > c(list(...)[[1]], eval(firstExpr, envir = parent.frame())) > } > > Calling g(x) correctly prints "global" twice, but calling f(x) > incorrectly prints > > [1] "global" "f" > > You can get the first element of ... without evaluating the rest using > ..1, but I don't know a way to do this for general n in pre-3.5.0 base R.Here's a way to do that: eval(as.name(paste0("..", n))) I was surprised this worked for n > 9, but it does. Looking at the source, I think the largest legal value for n is huge; you'd hit other limits long before n was too big. Duncan Murdoch
On Thu, May 3, 2018 at 9:50 AM, Duncan Murdoch <murdoch.duncan at gmail.com> wrote:> On 03/05/2018 11:18 AM, Duncan Murdoch wrote: >> >> On 03/05/2018 11:01 AM, William Dunlap via R-devel wrote: >>> >>> In R-3.5.0 you can use ...length(): >>> > f <- function(..., n) ...length() >>> > f(stop("one"), stop("two"), stop("three"), n=7) >>> [1] 3 >>> >>> Prior to that substitute() is the way to go >>> > g <- function(..., n) length(substitute(...())) >>> > g(stop("one"), stop("two"), stop("three"), n=7) >>> [1] 3 >>> >>> R-3.5.0 also has the ...elt(n) function, which returns >>> the evaluated n'th entry in ... , without evaluating the >>> other ... entries. >>> > fn <- function(..., n) ...elt(n) >>> > fn(stop("one"), 3*5, stop("three"), n=2) >>> [1] 15 >>> >>> Prior to 3.5.0, eval the appropriate component of the output >>> of substitute() in the appropriate environment: >>> > gn <- function(..., n) { >>> + nthExpr <- substitute(...())[[n]] >>> + eval(nthExpr, envir=parent.frame()) >>> + } >>> > gn(stop("one"), environment(), stop("two"), n=2) >>> <environment: R_GlobalEnv> >>> >> >> Bill, the last of these doesn't quite work, because ... can be passed >> down through a string of callers. You don't necessarily want to >> evaluate it in the parent.frame(). For example: >> >> x <- "global" >> f <- function(...) { >> x <- "f" >> g(...) >> } >> g <- function(...) { >> firstExpr <- substitute(...())[[1]] >> c(list(...)[[1]], eval(firstExpr, envir = parent.frame())) >> } >> >> Calling g(x) correctly prints "global" twice, but calling f(x) >> incorrectly prints >> >> [1] "global" "f" >> >> You can get the first element of ... without evaluating the rest using >> ..1, but I don't know a way to do this for general n in pre-3.5.0 base R. > > > Here's a way to do that: > > eval(as.name(paste0("..", n))) > > I was surprised this worked for n > 9, but it does. Looking at the source, > I think the largest legal value for n is huge; you'd hit other limits long > before n was too big.Maybe just get(paste0("..", n)) ? Hadley -- http://hadley.nz