@@ -103,17 +103,19 @@ Warn = function(msg, ...) rlang::warn(break_str(msg, init = "Warning: "), ...)
103103# ' Assert that a sliding computation function takes enough args
104104# '
105105# ' @param f Function; specifies a computation to slide over an `epi_df` or
106- # ' `epi_archive` in `epi_slide` or `epix_slide`.
106+ # ' `epi_archive` in `epi_slide` or `epix_slide`.
107107# ' @param ... Dots that will be forwarded to `f` from the dots of `epi_slide` or
108108# ' `epix_slide`.
109+ # ' @param n_mandatory_f_args Integer; specifies the number of arguments `f`
110+ # ' is required to take before any `...` arg. Defaults to 2.
109111# '
110112# ' @importFrom rlang is_missing
111113# ' @importFrom purrr map_lgl
112114# ' @importFrom utils tail
113115# '
114116# ' @noRd
115- assert_sufficient_f_args <- function (f , ... ) {
116- mandatory_f_args_labels <- c(" window data" , " group key" )
117+ assert_sufficient_f_args <- function (f , ... , n_mandatory_f_args = 2L ) {
118+ mandatory_f_args_labels <- c(" window data" , " group key" , " reference time value " )[seq( n_mandatory_f_args )]
117119 n_mandatory_f_args <- length(mandatory_f_args_labels )
118120 args = formals(args(f ))
119121 args_names = names(args )
@@ -181,6 +183,109 @@ assert_sufficient_f_args <- function(f, ...) {
181183 }
182184}
183185
186+ # ' Convert to function
187+ # '
188+ # ' @description
189+ # ' `as_slide_computation()` transforms a one-sided formula into a function.
190+ # ' This powers the lambda syntax in packages like purrr.
191+ # '
192+ # ' This code and documentation borrows heavily from [`rlang::as_function`]
193+ # ' (https://github.com/r-lib/rlang/blob/c55f6027928d3104ed449e591e8a225fcaf55e13/R/fn.R#L343-L427).
194+ # '
195+ # ' This code extends `rlang::as_function` to create functions that take three
196+ # ' arguments. The arguments can be accessed via the idiomatic `.x`, `.y`,
197+ # ' etc, positional references (`..1`, `..2`, etc), and also by `epi
198+ # ' [x]_slide`-specific names.
199+ # '
200+ # ' @source https://github.com/r-lib/rlang/blob/c55f6027928d3104ed449e591e8a225fcaf55e13/R/fn.R#L343-L427
201+ # '
202+ # ' @param x A function or formula.
203+ # '
204+ # ' If a **function**, it is used as is.
205+ # '
206+ # ' If a **formula**, e.g. `~ mean(.x$cases)`, it is converted to a function with up
207+ # ' to three arguments: `.x` (single argument), or `.x` and `.y`
208+ # ' (two arguments), or `.x`, `.y`, and `.z` (three arguments). The `.`
209+ # ' placeholder can be used instead of `.x`, `.group_key` can be used in
210+ # ' place of `.y`, and `.ref_time_value` can be used in place of `.z`. This
211+ # ' allows you to create very compact anonymous functions (lambdas) with up
212+ # ' to three inputs. Functions created from formulas have a special class. Use
213+ # ' `rlang::is_lambda()` to test for it.
214+ # '
215+ # ' If a **string**, the function is looked up in `env`. Note that
216+ # ' this interface is strictly for user convenience because of the
217+ # ' scoping issues involved. Package developers should avoid
218+ # ' supplying functions by name and instead supply them by value.
219+ # '
220+ # ' @param env Environment in which to fetch the function in case `x`
221+ # ' is a string.
222+ # ' @inheritParams rlang::args_dots_empty
223+ # ' @inheritParams rlang::args_error_context
224+ # ' @examples
225+ # ' f <- as_slide_computation(~ .x + 1)
226+ # ' f(10)
227+ # '
228+ # ' g <- as_slide_computation(~ -1 * .)
229+ # ' g(4)
230+ # '
231+ # ' h <- as_slide_computation(~ .x - .group_key)
232+ # ' h(6, 3)
233+ # '
234+ # ' @importFrom rlang check_dots_empty0 is_function new_function f_env
235+ # ' is_environment missing_arg f_rhs is_string is_formula caller_arg
236+ # ' caller_env global_env
237+ # '
238+ # ' @noRd
239+ as_slide_computation <- function (x ,
240+ env = global_env(),
241+ ... ,
242+ arg = caller_arg(x ),
243+ call = caller_env()) {
244+ check_dots_empty0(... )
245+
246+ if (is_function(x )) {
247+ return (x )
248+ }
249+
250+ if (is_formula(x )) {
251+ if (length(x ) > 2 ) {
252+ Abort(sprintf(" %s must be a one-sided formula" , arg ),
253+ class = " epiprocess__as_slide_computation__formula_is_twosided" ,
254+ epiprocess__x = x ,
255+ call = call )
256+ }
257+
258+ env <- f_env(x )
259+ if (! is_environment(env )) {
260+ Abort(" Formula must carry an environment." ,
261+ class = " epiprocess__as_slide_computation__formula_has_no_env" ,
262+ epiprocess__x = x ,
263+ epiprocess__x_env = env ,
264+ arg = arg , call = call )
265+ }
266+
267+ args <- list (
268+ ... = missing_arg(),
269+ .x = quote(..1 ), .y = quote(..2 ), .z = quote(..3 ),
270+ . = quote(..1 ), .group_key = quote(..2 ), .ref_time_value = quote(..3 )
271+ )
272+ fn <- new_function(args , f_rhs(x ), env )
273+ fn <- structure(fn , class = c(" epiprocess_slide_computation" , " function" ))
274+ return (fn )
275+ }
276+
277+ if (is_string(x )) {
278+ return (get(x , envir = env , mode = " function" ))
279+ }
280+
281+ Abort(sprintf(" Can't convert a %s to a slide computation" , class(x )),
282+ class = " epiprocess__as_slide_computation__cant_convert_catchall" ,
283+ epiprocess__x = x ,
284+ epiprocess__x_class = class(x ),
285+ arg = arg ,
286+ call = call )
287+ }
288+
184289# #########
185290
186291in_range = function (x , rng ) pmin(pmax(x , rng [1 ]), rng [2 ])
0 commit comments