Home > Net >  Using R, how to scope internal functions within a MAIN function?
Using R, how to scope internal functions within a MAIN function?

Time:08-29

My young son and I were playing a board game called Snails Pace. Simple enough, so I sat down to show him the game on the R-console.

Helper function

num.round = function(n, by=5)
    {
    byidx = (n %% by == 0); # these already are indexed well 
    new = by * as.integer((n   by) / by);
    res = n;
    res[!byidx] = new[!byidx];
    res;
    }

Primary function

snails.pace = function(moves = 200, finish.line = 8,
                            snail.x = NULL,
                            snail.y = NULL,
                            snail.col = NULL
                        )
    {
    if(is.null(snail.x))    { snail.x = 0*(1:6); }
    if(is.null(snail.y))    { snail.y = 1*(1:6); }
    if(is.null(snail.col)) { snail.col = c("orange", "blue", "pink", "green", "yellow", "red"); }
    
    snail.rank = 0*snail.x; 
    crank = 1; # current rank   
    move.number = 0;
    
    snails.plot = function(snail.x, snail.y, snail.rank, move.number, moves, finish.line, crank) 
        { 
        xmax = max(10, max(snail.x) );
        ymax = max(8, max(snail.y) );
        plot(snail.x, snail.y, 
                col=snail.col, 
                pch=16, cex=5, 
                xlim=c(0, num.round(xmax, 5) ), 
                ylim=c(0, num.round(ymax, 4) ), 
                axes=FALSE, 
                frame.plot=FALSE, 
                xlab="", ylab="",
                main=paste0("Move #", move.number, " of ", moves)
                ); 
        #axis(gr.side("bottom")); 
        axis(1);
            has.rank = (snail.rank != 0);
            snails.lab = paste0(snail.x, "*", snail.rank);
            snails.lab[!has.rank] = snail.x[!has.rank];
        text(snail.x, y=snail.y, labels=snails.lab, col="black"); 
        abline(v = finish.line, col="gray", lty="dashed");
        }
    snails.update = function(snail.x, snail.y, snail.rank, move.number, moves, finish.line, crank) 
        {
        x = readline(prompt="Press [enter] to continue, [ESC] to quit");
        n = sample(1:6, 1);
        snail.x[n] = 1   snail.x[n];
        if( (snail.rank[n] == 0) && (snail.x[n] >= finish.line) )
            {           
            snail.rank[n] = crank;
            crank = 1   crank;          
            # update to MAIN environment
            assign("snail.rank", snail.rank, envir=parent.frame() );
            assign("crank", crank, envir=parent.frame() );
            }       
        snail.x;
        }

    snails.plot(snail.x, snail.y, snail.rank, move.number, moves, finish.line, crank); 
    while(move.number < moves)
        {
        move.number = 1   move.number;
        snail.x = snails.update(snail.x, snail.y, snail.rank, move.number, moves, finish.line, crank);
        snails.plot(snail.x, snail.y, snail.rank, move.number, moves, finish.line, crank);      
        }
    }

Game play

snails.pace();

Question: how to scope internal functions within MAIN environoment?

The MAIN function is snails.pace(). You will notice in the internal function snails.update, I update two variables and assign them back to the MAIN scope using assign.

Is there a way at the MAIN level I can define all the variables and just USE them within all internal functions without having to assign them back or returning the updating values?

As you can see in my CODE, I call all of the variables into the functions and either "back assign" or return any changes. I would prefer to just set a new env() or something and have MAIN work like R-Global seems to. Any suggestions on how to do that?

That is, my internal functions would not pass anything in: snails.plot = function() and snails.update = function() AS they would get the LOCAL environment variables (defined as within MAIN defined as snails.pace()). And ideally update the LOCAL environment variables by updating the value within the internal function.

Update

So it appears that I can drop the function passing. See:


snails.pace2 = function(moves = 200, finish.line = 8,
                            snail.x = NULL,
                            snail.y = NULL,
                            snail.col = NULL
                        )
    {
    if(is.null(snail.x))    { snail.x = 0*(1:6); }
    if(is.null(snail.y))    { snail.y = 1*(1:6); }
    if(is.null(snail.col))  { snail.col = c("orange", "blue", "pink", "green", "yellow", "red"); }
    
    snail.rank = 0*snail.x; 
    crank = 1; # current rank   
    move.number = 0;
    
    snails.plot = function() 
        { 
        xmax = max(10, max(snail.x) );
        ymax = max(8, max(snail.y) );
        plot(snail.x, snail.y, 
                col=snail.col, 
                pch=16, cex=5, 
                xlim=c(0, num.round(xmax, 5) ), 
                ylim=c(0, num.round(ymax, 4) ), 
                axes=FALSE, 
                frame.plot=FALSE, 
                xlab="", ylab="",
                main=paste0("Move #", move.number, " of ", moves)
                ); 
        #axis(gr.side("bottom")); 
        axis(1);
            has.rank = (snail.rank != 0);
            snails.lab = paste0(snail.x, "*", snail.rank);
            snails.lab[!has.rank] = snail.x[!has.rank];
        text(snail.x, y=snail.y, labels=snails.lab, col="black"); 
        abline(v = finish.line, col="gray", lty="dashed");
        }
    snails.update = function() 
        {
        x = readline(prompt="Press [enter] to continue, [ESC] to quit");
        n = sample(1:6, 1);
        snail.x[n] = 1   snail.x[n];
        if( (snail.rank[n] == 0) && (snail.x[n] >= finish.line) )
            {           
            snail.rank[n] = crank;
            crank = 1   crank;          
            # update to MAIN environment
            assign("snail.rank", snail.rank, envir=parent.frame() );
            assign("crank", crank, envir=parent.frame() );
            }       
        snail.x;
        }

    snails.plot(); 
    while(move.number < moves)
        {
        move.number = 1   move.number;
        snail.x = snails.update();
        snails.plot();      
        }
    }

@MrFlick is correct about the lexical scoping, if I understand the above correctly. If an internal updates something from MAIN, it has to assign it back to MAIN I guess <<- or assign ... parent. Is there not a way to tell the internal SUBFUNCTIONS to SCOPE at the same level of MAIN?

CodePudding user response:

To have a function f defined within another function main such that f has the same scope as main surround the entire body of f with eval.parent(substitute({...})) like this:

main <- function() {
  f <- function() eval.parent(substitute({
    a <- a   1
  }))

  a <- 1
  f()
  f()
  10 * a
}

main()
## [1] 30

The gtools package has defmacro which allows the same thing and uses the same technique internally. Also see the wrapr package.

CodePudding user response:

There are two completely different concepts called "parent" in R: the parent.frame() of a call, and the parent.env() of an environment.

parent.frame() walks up the chain of the stack of calls. If you have a recursive function that calls itself, it will appear multiple times in that chain.

In general, it's dangerous to use parent.frame(), because even if the context in which you use it now makes it clear which environment will be the parent.frame(), at some future time you might change your program (e.g. make the internal function into a recursive one, or call it from another internal function), and then parent.frame() will refer to something different.

The parent.env() function applies to an environment; parent.env(environment()) gives you the enclosing environment of the current one. If you call parent.env(environment()) it will always refer to the environment where your current function was defined. It doesn't matter how you called it, just how you defined it. So you always know what will happen if you assign there, and it's much safer in the long term than using parent.frame().

The <<- "super-assignment" works with enclosing environments, not the stack of calls. If you do var <<- value, then as long as you are sure that var was defined in the enclosing function, you can be sure that's what gets modified.

One flaw in R is that it doesn't enforce the existence of var there, so that's why some people say <<- is "sloppy". If you accidentally forget to define it properly, or spell it wrong, R will search back through the whole chain of environments to try to do what you asked, and if it never finds a matching variable, it will do the assignment in the global environment. You almost never want to do that: keep side effects minimal.

So, to answer the question "Is there a way at the MAIN level I can define all the variables and just USE them within all internal functions without having to assign them back or returning the updating values?": as you found in your edit, the nested function can read the value of any variable in the MAIN function without requiring any special code. To modify those variables, be sure both snail.rank and crank are defined in MAIN, then use <<- in the nested function to assign new values to them.

  • Related