You can achieve this using metaprogramming in R.
@alexis_laz's approach was in fact already metaprogramming.
However, he used strings which are a dirty hack and error prone. So you did well to reject it.
The correct way to approach @alexis_laz's approach would be by wrangling on code level. In base R this is done using substitute()
. There are however better packages e.g. rlang
by Hadley Wickham. But I give you a base R solution (less dependency).
lapply_ <- function(lst, FUN) {
eval.parent(
substitute(
callCC(function(return_) {
lapply(lst_, FUN_)
}),
list(lst_ = lst, FUN_=substitute(FUN))))
}
Your SHORT_CIRCUIT
function is actually a more general, control flow return
function (or a break
function which takes an argument to return it). Thus, I call it return_
.
We want to have a lapply_
function, in which we can in the FUN=
part use a return_
to break
out of the usual lapply()
.
As you showed, this is the aim:
callCC(
function (return_) {
lapply(1:1000, function (x) if (x == 100) return_(x))
}
)
Just with the problem, that we want to be able to generalize this expression.
We want
callCC(
function(return_) lapply(lst, FUN_)
)
Where we can use inside the function definition we give for FUN_
the return_
.
We can let, however, the function defintion see return_
only if we insert the function definition code into this expression.
This exactly @alexis_laz tried using string and eval.
Or you did this by manipulating environment variables.
We can safely achieve the insertion of literal code using substitute(expr, replacer_list)
where expr
is the code to be manipulated and replacer_list
is the lookup table for the replacement of code.
By substitute(FUN)
we take the literal code given for FUN=
for lapply_
without evaluating it. This expression returns literal quoted code (better than the string in @alexis_laz's approach).
The big substitute
command says: "Take the expression callCC(function(return_) lapply(lst_, FUN_))
and replace lst_
in this expression by the list given for coll
and FUN_
by the literal quoted expression given for FUN
.
This replaced expression is then evaluated in the parent environment (eval.parent()
) meaning: the resulting expression replaces the lapply_()
call and is executed exactly where it was placed.
Such use of eval.parent()
(or eval( ... , envir=parent.frame())
) is fool proof. (otherwise, tidyverse packages wouldn't be production level ...).
So in this way, you can generalize callCC()
calls.
lapply_(1:1000, FUN=function(x) if (x==100) return_(x))
## [1] 100