rlang icon indicating copy to clipboard operation
rlang copied to clipboard

Any interest in `capture_args()`?

Open t-kalinowski opened this issue 3 years ago • 2 comments

In keras, I've implemented a pure-R version of a helper function named capture_args(). It's intent is to capture the values of what the user called a function with, as if they had written base::list(...) instead of <keras_fn>(...).

This is useful because it lets the keras R package easily maintain compatibility with older versions of tensorflow, while still presenting only the latest function signatures in the R wrappers.

Due to some peculiarities of how it's implemented, it has some inconvenient restrictions such as, match.call() must be part of the call, capture_args() itself can't be evaluated in another call (e.g., do.call(fn, capture_args() fails), it potentially reorders arguments that matched to ... in the originating call, and, it can't handle positional-only unnamed args in .... These deficiencies would probably best be resolved by re-writing it in C, but we currently don't have any compiled code in the keras package and I'd rather keep keras pure R if possible. Keras already has an rlang dependency however. Would there be any interest in adding a better, implemented-in-C capture_args() to rlang? (or maybe there is something that already does this in rlang that I overlooked?)

t-kalinowski avatar Feb 09 '22 15:02 t-kalinowski

Why can't it handle positional args in dots?

it has some inconvenient restrictions such as, match.call() must be part of the call, capture_args() itself can't be evaluated in another call (e.g., do.call(fn, capture_args() fails)

You could try with frame_call() and frame_fn(). Also needs frame_caller() which you can implement with eval_bare(call2(caller_env), frame).

lionel- avatar Feb 09 '22 15:02 lionel-

Why can't it handle positional args in dots?

Because matching to named arguments can reorder positions:

unlockBinding(quote(assert_all_dots_named), asNamespace("keras"))
evalq({
  assert_all_dots_named <- function(...) {}
}, envir = asNamespace("keras"))


fn <- function(b = NULL, ...) {
  keras:::capture_args(match.call())
}

list("first", b = "second", "third")
#> [[1]]
#> [1] "first"
#> 
#> $b
#> [1] "second"
#> 
#> [[3]]
#> [1] "third"
fn("first", b = "second", "third")
#> $b
#> [1] "second"
#> 
#> [[2]]
#> [1] "first"
#> 
#> [[3]]
#> [1] "third"

Created on 2022-02-09 by the reprex package (v2.0.1)

This restriction doesn't matter so much when we're going out to Python, but the guardrail is there because positions won't always be the same as the user provided, so positional capture is disabled.

t-kalinowski avatar Feb 09 '22 16:02 t-kalinowski