Henrik Bengtsson
2022-Feb-24 19:21 UTC
[Rd] [External] DOCS/BUG?: opts <- base::.Options is *not* a copy
On Thu, Feb 24, 2022 at 5:23 AM <luke-tierney at uiowa.edu> wrote:> > On Thu, 24 Feb 2022, Henrik Bengtsson wrote: > > > Hi, is the following a non-documented feature or a bug: > > > > $ R --quiet --vanilla > > opts <- base::.Options > > opts[["abc"]] > > #> NULL > > options(abc = 42) > > opts[["abc"]] > > #> [1] 42 > > > > I would have expected that 'opts' would be a *copy* of base::.Options > > that is not affected by later changes to base::.Options. FWIW, the > > same happens if we try with: > > > > opts <- .BaseNamespaceEnv[[".Options"]] > > > > I don't think lazy evaluation is involved, because I evaluate > > opts[["abc"]] above. > > > > The only thing help(".Options") says is: > > > > Note: > > For compatibility with S there is a visible object .Options whose > > value is a pairlist containing the current options() (in no particular > > order). Assigning to it will make a local copy and not change the > > original. (Using it however is faster than calling options()). > > You are misreading what this says. As with any assignment to an object > that has other references, the assignment will create a local copy > before mutating.I don't think I misread it; that's exactly how I interpreted it, too. I just copied that passage to help the reader see that there's nothing in the docs mentioning this behavior.> It does not say that referencing the object makes a > copy. So there is no inconsistency.I'd argue that this behavior of .Options (because of how options() is implemented) is not a standard procedure in R, and that the expectation would be to make a copy unless otherwise documented. You need to dig into the code to figure out that this is not the case and how it works. For example, my mental model of how this is implemented is something like: .Options <- pairlist() options <- function(...) { args <- list(...) old <- new <- .Options for (name in names(args)) new[[name]] <- args[[name]] assignInMyNamespace(".Options", new) invisible(old) } and that does create a copy. I only discovered this because I added the following to my package tests: opts <- base::.Options call_random_fcn() stopifnot(identical(base::.Options, opts)) Turns out it never failed. I use .Options, because it's faster than options(), which always sorts elements by their names. FWIW, the workaround is to force a copy using opts <- as.list(base::.Options).> That options() modifies the value of an object with multiple > references is not ideal, but changing that while maintaining .Object > as a regular variable is probably more trouble than it is worth. It is > probably time to work towards deprecating .Options (maybe turning it > into an active binding that does make a copy). At the very least > discouraging its use in the help file.This sounds good to me. Thanks, Henrik> > Other that changing the docs I doubt this will ever get high enough on > anyone's priority list to get done > > Best, > > luke > > > > > This behavior goes back to at least R 2.15.0. > > > > /Henrik > > > > ______________________________________________ > > R-devel at r-project.org mailing list > > https://stat.ethz.ch/mailman/listinfo/r-devel > > > > -- > Luke Tierney > Ralph E. Wareham Professor of Mathematical Sciences > University of Iowa Phone: 319-335-3386 > Department of Statistics and Fax: 319-335-3017 > Actuarial Science > 241 Schaeffer Hall email: luke-tierney at uiowa.edu > Iowa City, IA 52242 WWW: http://www.stat.uiowa.edu
Martin Maechler
2022-Feb-25 08:59 UTC
[Rd] [External] DOCS/BUG?: opts <- base::.Options is *not* a copy
>>>>> Henrik Bengtsson >>>>> on Thu, 24 Feb 2022 11:21:05 -0800 writes:> On Thu, Feb 24, 2022 at 5:23 AM <luke-tierney at uiowa.edu> wrote: >> >> On Thu, 24 Feb 2022, Henrik Bengtsson wrote: >> >> > Hi, is the following a non-documented feature or a bug: >> > >> > $ R --quiet --vanilla >> > opts <- base::.Options >> > opts[["abc"]] >> > #> NULL >> > options(abc = 42) >> > opts[["abc"]] >> > #> [1] 42 >> > >> > I would have expected that 'opts' would be a *copy* of base::.Options >> > that is not affected by later changes to base::.Options. FWIW, the >> > same happens if we try with: >> > >> > opts <- .BaseNamespaceEnv[[".Options"]] >> > >> > I don't think lazy evaluation is involved, because I evaluate >> > opts[["abc"]] above. >> > >> > The only thing help(".Options") says is: >> > >> > Note: >> > For compatibility with S there is a visible object .Options whose >> > value is a pairlist containing the current options() (in no particular >> > order). Assigning to it will make a local copy and not change the >> > original. (Using it however is faster than calling options()). >> >> You are misreading what this says. As with any assignment to an object >> that has other references, the assignment will create a local copy >> before mutating. > I don't think I misread it; that's exactly how I interpreted it, too. > I just copied that passage to help the reader see that there's nothing > in the docs mentioning this behavior. >> It does not say that referencing the object makes a >> copy. So there is no inconsistency. > I'd argue that this behavior of .Options (because of how options() is > implemented) is not a standard procedure in R, and that the > expectation would be to make a copy unless otherwise documented. You > need to dig into the code to figure out that this is not the case and > how it works. For example, my mental model of how this is implemented > is something like: > .Options <- pairlist() > options <- function(...) { > args <- list(...) > old <- new <- .Options > for (name in names(args)) new[[name]] <- args[[name]] > assignInMyNamespace(".Options", new) > invisible(old) > } > and that does create a copy. > I only discovered this because I added the following to my package tests: > opts <- base::.Options > call_random_fcn() > stopifnot(identical(base::.Options, opts)) > Turns out it never failed. I use .Options, because it's faster than > options(), which always sorts elements by their names. FWIW, the > workaround is to force a copy using opts <- as.list(base::.Options). Esthetics only: I think I'd rather use opts <- force(base::.Options) >> That options() modifies the value of an object with multiple >> references is not ideal, but changing that while maintaining .Object >> as a regular variable is probably more trouble than it is worth. It is >> probably time to work towards deprecating .Options (maybe turning it >> into an active binding that does make a copy). At the very least >> discouraging its use in the help file. > This sounds good to me. We might consider `options()` gaining an optional argument, say `sortNames = TRUE` and `options(sortNames=FALSE)` would still not sort and hence be faster than pure options() Martin > Thanks, > Henrik >> >> Other that changing the docs I doubt this will ever get high enough on >> anyone's priority list to get done >> >> Best, >> >> luke >> >> > >> > This behavior goes back to at least R 2.15.0. >> > >> > /Henrik >> > >> > ______________________________________________ >> > R-devel at r-project.org mailing list >> > https://stat.ethz.ch/mailman/listinfo/r-devel >> > >> >> -- >> Luke Tierney >> Ralph E. Wareham Professor of Mathematical Sciences >> University of Iowa Phone: 319-335-3386 >> Department of Statistics and Fax: 319-335-3017 >> Actuarial Science >> 241 Schaeffer Hall email: luke-tierney at uiowa.edu >> Iowa City, IA 52242 WWW: http://www.stat.uiowa.edu > ______________________________________________ > R-devel at r-project.org mailing list > https://stat.ethz.ch/mailman/listinfo/r-devel