I have a function foo()
that I'd like to be able to call in two different "modes": once or within a while loop.
I thought using a somewhat generic wrapper (runtime_gateway()
) and passing arguments via ...
to foo()
would make sense here so I could leverage the same "runtime gateway logic" for arbitrary functions with different sets of arguments.
If run foo()
within a while loop, I'd like to update some of its arguments while keeping the default or passed values of other arguments.
How would I do that?
I'm aware of rlang::dot_list(...)
and friends and had a quick glance at https://github.com/r-lib/ellipsis. It seems that any of those would only let pluck values from or inspect ellipsis content, but I don't see how I could "update it in transit".
foo <- function(
id = "id_a",
at = Sys.time()
) {
message(stringr::str_glue("{id}: {at}"))
Sys.sleep(1)
}
runtime_gateway <- function(
fun = foo,
run_mode = c("once", "while"),
... # Args to be passed to `fun`
) {
run_mode <- match.arg(run_mode)
if (run_mode == "once") {
fun(...)
} else if (run_mode == "while") {
counter <- 0
while(counter < 3) {
# Goal: keep ellipsis value for `id` but *update* value for `at`
dots <- rlang::dots_list(...)
at <- if ("at" %in% names(dots)) {
message("`at` was passed via ellipsis:")
message(dots$at)
dots$at
} else {
Sys.time()
}
fun(at = at + 60, ...)
counter <- counter + 1
}
}
}
runtime_gateway()
#> id_a: 2020-02-21 14:09:16.779
runtime_gateway(at = lubridate::ymd_hms("2020-02-21 10:30:00"))
#> id_a: 2020-02-21 10:30:00
runtime_gateway(run_mode = "while")
#> id_a: 2020-02-21 14:10:18.897
#> id_a: 2020-02-21 14:10:19.900
#> id_a: 2020-02-21 14:10:20.902
runtime_gateway(run_mode = "while", id = "id_b")
#> id_b: 2020-02-21 14:10:21.905
#> id_b: 2020-02-21 14:10:22.906
#> id_b: 2020-02-21 14:10:23.908
runtime_gateway(run_mode = "while", at = lubridate::ymd_hms("2020-02-21 10:30:00"))
#> `at` was passed via ellipsis:
#> 2020-02-21 10:30:00
#> Error in fun(at = at + 60, ...): formal argument "at" matched by multiple actual arguments
Created on 2020-02-21 by the reprex package (v0.3.0)
You could ensure that dots
contains an at
argument by adding it if it isn't present, then dispatch fun
using dots
instead of ...
with do.call
runtime_gateway <- function(
fun = foo,
run_mode = c("once", "while"),
... # Args to be passed to `fun`
) {
run_mode <- match.arg(run_mode)
if (run_mode == "once") {
fun(...)
} else if (run_mode == "while") {
counter <- 0
while(counter < 3) {
# Goal: keep ellipsis value for `id` but *update* value for `at`
dots <- rlang::dots_list(...)
if ("at" %in% names(dots)) {
message("`at` was passed via ellipsis:")
message(dots$at)
dots$at <- dots$at + 60
} else {
dots$at <- Sys.time() + 60
}
do.call(fun, dots)
counter <- counter + 1
}
}
}
And here's the output:
runtime_gateway()
#> id_a: 2020-02-21 14:22:07
runtime_gateway(at = lubridate::ymd_hms("2020-02-21 10:30:00"))
#> id_a: 2020-02-21 10:30:00
runtime_gateway(run_mode = "while")
#> id_a: 2020-02-21 14:23:09
#> id_a: 2020-02-21 14:23:10
#> id_a: 2020-02-21 14:23:11
runtime_gateway(run_mode = "while", id = "id_b")
#> id_b: 2020-02-21 14:23:12
#> id_b: 2020-02-21 14:23:13
#> id_b: 2020-02-21 14:23:14
runtime_gateway(run_mode = "while", at = lubridate::ymd_hms("2020-02-21 10:30:00"))
#> `at` was passed via ellipsis:
#> 2020-02-21 10:30:00
#> id_a: 2020-02-21 10:31:00
#> `at` was passed via ellipsis:
#> 2020-02-21 10:30:00
#> id_a: 2020-02-21 10:31:00
#> `at` was passed via ellipsis:
#> 2020-02-21 10:30:00
#> id_a: 2020-02-21 10:31:00
Created on 2020-02-21 by the reprex package (v0.3.0)