[R] environments: functions within functions

Sarah Goslee @@r@h@go@|ee @end|ng |rom gm@||@com
Fri May 26 21:36:56 CEST 2023


Very nice, thank you!


Sarah

On Thu, May 25, 2023 at 8:20 PM Iris Simmons <ikwsimmo using gmail.com> wrote:
>
> Hi,
>
>
> I think there are two easy ways to fix this. The first is to use a `switch` to call the intended function, this should not be a problem since there are a small number of print functions in **mixR**
>
> ```R
> print.mixfitEM <- function (x, digits = getOption("digits"), ...)
> {
>     switch(x$family,
>     gamma   = printgamma  (x, digits),
>     lnorm   = printlnorm  (x, digits),
>     normal  = printnormal (x, digits),
>     weibull = printweibull(x, digits),
>     stop(gettextf("invalid '%s' value", "x$family", domain = "R")))
>     invisible(x)
> }
> environment(print.mixfitEM) <- getNamespace("mixR")
> print.mixfitEM <- compiler::cmpfun(print.mixfitEM)
> ```
>
> This is nice because 'x' is no longer evaluated twice (you could try this yourself with something like `mixR:::print.mixfitEM(writeLines("testing"))`, you'll see the output twice, once for `x$family` and a second for evaluating `match.call()` expression), it follows standard evaluation, and 'x' is returned invisibly at the end, like most other `print` methods. If you really wanted to continue using `eval`, you could instead do something like
>
> ```R
> print.mixfitEM <- function (x, digits = getOption("digits"), ...)
> {
>     expr <- quote(printfunction(x, digits))
>     expr[[1L]] <- as.symbol(paste0("print", x$family))
>     eval(expr)
>     invisible(x)
> }
> environment(print.mixfitEM) <- getNamespace("mixR")
> print.mixfitEM <- compiler::cmpfun(print.mixfitEM)
> ```
>
> This also solves the same issues, but it's ugly and slower.
>
> At least for now, I would copy one of the functions above into the site-wide startup profile file or your user profile, along with
>
> ```R
> utils::assignInNamespace("print.mixfitEM", print.mixfitEM, "mixR")
> ```
>
> This does have the unfortunate side effect of loading **mixR** every time an R session is launched, but you could also put it inside another function like:
>
> ```R
> fix.mixR.print.mixfitEM <- function ()
> {
>     print.mixfitEM <- function(x, digits = getOption("digits"), ...) {
>         switch(x$family,
>         gamma   = printgamma  (x, digits),
>         lnorm   = printlnorm  (x, digits),
>         normal  = printnormal (x, digits),
>         weibull = printweibull(x, digits),
>         stop(gettextf("invalid '%s' value", "x$family", domain = "R")))
>         invisible(x)
>     }
>     environment(print.mixfitEM) <- getNamespace("mixR")
>     print.mixfitEM <- compiler::cmpfun(print.mixfitEM)
>     utils::assignInNamespace("print.mixfitEM", print.mixfitEM, "mixR")
> }
> ```
>
> which you would then call in your scripts before using **mixR**. I hope this helps!
>
> On Thu, May 25, 2023 at 10:19 AM Sarah Goslee <sarah.goslee using gmail.com> wrote:
>>
>> Hi,
>>
>> I ran into a problem with S3 method dispatch and scoping while trying
>> to use functions from the mixR package within my own functions. I know
>> enough to find the problem (I think!), but not enough to fix it
>> myself. The problem isn't really a package-specific problem, so I'm
>> starting here, and will file an issue with the maintainer once I have
>> a solution.
>>
>> Detailed explanation below, but briefly, the S3 methods in this
>> package use match.call() and then eval() to select the correct
>> internal method. This works fine from the command line, but if the
>> method is called from within another function, the use of
>> environment() within eval() means that the objects passed to the
>> wrapper function are no longer visible within the eval() call.
>>
>> I have a two-part question:
>> A. How do I get around this right now?
>> B. What would the correct approach be for the package authors?
>>
>> library(mixR)
>>
>> # first example from ?mixfit
>> ## fitting the normal mixture models
>> set.seed(103)
>> x <- rmixnormal(200, c(0.3, 0.7), c(2, 5), c(1, 1))
>> data <- bin(x, seq(-1, 8, 0.25))
>> fit1 <- mixfit(x, ncomp = 2)  # raw data
>> rm(x, data)
>> ###
>>
>> # simple function
>> funworks <- function(x) {
>>     print(x)
>> }
>>
>> ###
>>
>> # almost identical simple function
>> funfails <- function(thisx) {
>>     print(thisx)
>> }
>>
>> ###
>>
>> funworks(fit1)
>> funfails(fit1)
>>
>> #######
>>
>> The explanation as I understand it...
>>
>> print called on this object gets passed to print.mixfitEM(), which is:
>>
>>
>> function (x, digits = getOption("digits"), ...)
>> {
>>     family <- x$family
>>     mc <- match.call()
>>     mc$digits <- digits
>>     fun.name <- paste0("print", family)
>>     mc[[1]] <- as.name(fun.name)
>>     eval(mc, environment())
>> }
>>
>>
>> Working through the calls, when eval() is called from within funfails(), mc is
>> printnormal(x = thisx, digits = 7)
>> and the calling environment does not contain thisx.
>>
>> In funworks(), it's
>> printnormal(x = x, digits = 7)
>>
>> and x is found.
>>
>> So, I can get around the problem by naming my argument x, as in
>> funworks(), but that's unsatisfying. Is there something else I can do
>> to get my functions to work?
>>
>> And what's the correct way to do what print.mixfitEM() is doing, so
>> that it works regardless? I poked around for a while, but didn't find
>> a clear (to me!) answer.
>>
>> Thanks,
>> Sarah
>>



More information about the R-help mailing list