I recently learned about quosures and tidy evaluation, and it feels very magical .
But because of the whole "a-little-bit-of-knowledge-is-a-dangerous-thing"-situation, I want to make sure I'm using it right .
Requirements:
- Let's say I have some vector
x
for which I would like to implement a fairly expensive functioncompute_a_lot()
. - Let's say further that
compute_a_lot()
should also work on subsets of x, sayx[2:5]
, and that, crucially, when working on the subset, its result should depend on some info about allx
(say, implausibly,length(x)
). - Assume that I have already implemented a method for
'[.x'
to retain attributes on subsetting.
(My real use case is a print method for x
, which needs to ensure all subsets of x
are scaled equally).
Two naive solutions:
- A naive solution of this problem would be to run
compute_a_lot()
wheneverx
is created, and to save the result as an attribute ofx
, which a method ofcompute_a_lot()
could then just return when called onx[1:2]
. But that would be very painful for the user, becausecompute_a_lot()
would be run before it is actually needed, if ever. - Another naive solution would be to just to save the whole
x
as an attribute ofx
, which would be retained on subsetting and available forcompute_a_lot()
ofx[1:2]
. But that just seems disgusting.
So I thought, hey, I know a solution – quosures!
whenever x
is created, I run something like
attr(x, "compute_a_lot_value") <- rlang::quo(compute_a_lot(x, ...))
Then, when compute_a_lot()
is actually called on some x[1:3]
and it's result needed , I run
rlang::eval_tidy(attr(x, "compute_a_lot"))
This seems to accomplish what I want: x
always carries around with it the instructions (expression + environment) to calculate compute_a_lot()
, but these instructions are acted upon only when it's really necessary.
I understand that quosures are mostly used in the context of NSE, so I'm a little worried I'm using it right.
Is this (delaying computation) a proper use of quosures?
Ps.: The whole thing happens in the context of x
being an S3 class and print methods, but it seemed like that wasn't necessary to reprex here for the main issue.
Pps.: I am vaguely aware of / very excited about promises and future for long-running computations, but asynchronicity (?) is not the issue here, but just plain delaying the computation.