[R] environments: functions within functions

Duncan Murdoch murdoch@dunc@n @end|ng |rom gm@||@com
Thu May 25 17:50:39 CEST 2023


On 25/05/2023 10:18 a.m., Sarah Goslee 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?

I'll try B first.  The problem is that they want to look up fun.name in 
the environment that is visible from  print.mixfitEM(), i.e. the mixR 
internal namespace environment, and they also want to evaluate that 
function the way it was evaluated when print.mixfitEM was called, i.e. 
in parent.frame().  R doesn't support that kind of thing in one step, so 
they should do it in two steps, e.g. rewriting print.mixfitEM something 
like this:

function (x, digits = getOption("digits"), ...)
{
     family <- x$family
     mc <- match.call()
     mc$digits <- digits
     fun.name <- paste0("print", family)
     e <- new.env(parent = parent.frame())
     e[[fun.name]] <- get(fun.name)
     mc[[1]] <- as.name(fun.name)
     eval(mc, e)
}

This tries to get the function ("printnormal") from the local evaluation 
environment, but there's nothing there named "printnormal", so it goes 
to the parent environment, and gets it there.  It puts it in a new 
environment whose parent is parent.frame() and everything works.

For part A of your question, I can't think of any workaround other than 
using x as the name of the variable that is passed to print().  You 
could hide that fact by making a local function to do it, e.g.

   myprint <- function(x) print(x)

and then calling myprint(fit1).

Duncan Murdoch


> 
> 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