[R] Question about function scope

Neal Fultz n|u|tz @end|ng |rom gm@||@com
Wed Oct 31 02:28:53 CET 2018


I'm going to n-th Duncan's recommendations to try to keep your functions
small and try not to mess with environments.

... So I'm ashamed that I wrote the following - I apologize in advance.

But as an intellectual exercise, we can cannibalize the code for `dynGet`
and create a `dynSet` function which mirrors it:

---

dynSet <- function (x, value, ifnotfound = stop(gettextf("%s not found",
sQuote(x)),
                               domain = NA), minframe = 1L, inherits =
FALSE)
{
  n <- sys.nframe()
  myObj <- structure(list(.b = as.raw(7)), foo = 47L)
  while (n > minframe) {
    n <- n - 1L
    env <- sys.frame(n)
    r <- get0(x, envir = env, inherits = inherits, ifnotfound = myObj)
    if (!identical(r, myObj)) {
      assign(x, value, envir = env)
      return(NULL)
    }
  }
  ifnotfound
}

### Note that in bar1 / bar2, you can't use x,y,z - dynSet will match the
local variable instead of the calling frame.

bar1 <- function(){
  x1 <- 1
  y1 <- 1
  z1 <- 1
  cat(sprintf('bar1: x=%d, y=%d, z=%d\n', x1, y1, z1))
  dynSet("x", x1)
  dynSet("y", y1)
  dynSet("z", z1)

}

bar2 <- function(){
  x2 <- 2
  y2 <- 2
  z2 <- 2
  cat(sprintf('bar2: x=%d, y=%d, z=%d\n', x2, y2, z2))
  dynSet("x", x2)
  dynSet("y", y2)
  dynSet("z", z2)

}

foo <- function(a=1, b=2, c=0){

  # some setup code
  dummy <- a + b
  x <- y <- z <- 0

  # here is my scope problem
  if (c==1) bar1()
  if (c==2) bar2()

  # some more code
  cat(sprintf('foo: x=%d, y=%d, z=%d\n', x, y, z))

}

foo(c=0)
foo(c=1)
foo(c=2)




---

Please don't actually do this though.

I think the most educational part of this is the dynGet/dynSet function
itself - if you understand the various functions it uses, you can do pretty
much anything in R.


But again, you shouldn't.





On Tue, Oct 30, 2018 at 1:51 PM Duncan Murdoch <murdoch.duncan using gmail.com>
wrote:

> On 30/10/2018 4:18 PM, Sebastien Bihorel wrote:
> > Thanks Duncan for your quick reply.
> >
> > Ideally, I would want bar1 and bar2 to be independent functions, because
> they are huge in actuality and, as the actual foo function grows, I may end
> up with 10 different bar# functions. So I would like to separate them from
> foo as much as possible.
>
> If that's the case, then I think the second solution (passing around
> environments) is a really bad idea.  Functions should not have side
> effects because it makes them harder to understand.  Modifying local
> variables in some other function is a really dangerous side effect,
> especially if both functions are big, because they are already hard to
> understand.
>
> If you really have more callers for bar1() than just foo(), it is even
> worse.
>
> So I'd suggest having bar1 and bar2 return the new values in a list, and
> in foo(), explicitly extract the values you want from the list.  Then if
> in the future you decide that bar1 should also return a 4th value w, or
> you want to rename x to something more meaningful, you don't need to
> check your other foo functions to see if x and w are used in the same
> way in them.  They'll just ignore w if it isn't relevant to them.  Your
> foo() code becomes something like this:
>
>    x <- y <- z <- 0
>
>    # here is my scope problem
>    result <- list(x = x, y = y, z = z) # c == 0 case
>    if (c==1) result <- bar1()
>    if (c==2) result <- bar2()
>    x <- result[["x"]]
>    y <- result[["y"]]
>    z <- result[["z"]]
>
> Duncan Murdoch
>
>
> >
> >
> > ----- Original Message -----
> > From: "Duncan Murdoch" <murdoch.duncan using gmail.com>
> > To: "Sebastien Bihorel" <sebastien.bihorel using cognigencorp.com>,
> r-help using r-project.org
> > Sent: Tuesday, October 30, 2018 4:13:05 PM
> > Subject: Re: [R] Question about function scope
> >
> > On 30/10/2018 3:56 PM, Sebastien Bihorel wrote:
> >> Hi,
> >>
> >>   From the R user manual, I have a basic understanding of the scope of
> function evaluation but have a harder time understanding how to mess with
> environments.
> >>
> >> My problem can be summarized by the code shown at the bottom:
> >> - the foo function performs some steps including the assignment of
> default values to 3 objects: x, y, z
> >> - at some point, I would like to call either the bar1 or bar2 function
> based upon the value of the c argument of the foo function. These functions
> assign different values to the x, y, z variables.
> >> - then foo should move on and do other cool stuff
> >>
> >> Based upon default R scoping, the x, y, and z variables inside the bar1
> and bar2 functions are not in the same environment as the x, y, and z
> variables created inside the foo function.
> >>
> >> Can I modify the scope of evaluation of bar1 and bar2 so that x, y, and
> z created inside the foo function are modified?
> >>
> >> PS:
> >> - I know about "<<-" but, in my real code (which I cannot share,
> sorry), foo is already called within other functions and x, y, and z
> variables do not exist in the top-level environment and are not returned by
> foo. So "<<-" does not work (per manual: " Only when <<- has been used in a
> function that was returned as the value of another function will the
> special behavior described here occur. ")
> >
> > I haven't looked up that quote, but it is likely describing a situation
> > that isn't relevant to you.  For you, the important part is that bar1
> > and bar2 must be created within foo.  They don't need to be returned
> > from it.
> >
> > So my edit below of your code should do what you want.
> >
> > foo <- function(a=1, b=2, c=0){
> >
> >     bar1 <- function(){
> >       x <<- 1
> >       y <<- 1
> >       z <<- 1
> >       cat(sprintf('bar1: x=%d, y=%d, z=%d\n', x, y, z))
> >     }
> >
> >     bar2 <- function(){
> >       x <<- 2
> >       y <<- 2
> >       z <<- 2
> >       cat(sprintf('bar2: x=%d, y=%d, z=%d\n', x, y, z))
> >     }
> >
> >     # some setup code
> >     dummy <- a + b
> >     x <- y <- z <- 0
> >
> >     # here is my scope problem
> >     if (c==1) bar1()
> >     if (c==2) bar2()
> >
> >     # some more code
> >     cat(sprintf('foo: x=%d, y=%d, z=%d\n', x, y, z))
> >
> > }
> >
> > foo(c=0)
> > foo(c=1)
> > foo(c=2)
> >
> > I get this output:
> >
> >   > foo(c=0)
> > foo: x=0, y=0, z=0
> >   > foo(c=1)
> > bar1: x=1, y=1, z=1
> > foo: x=1, y=1, z=1
> >   > foo(c=2)
> > bar2: x=2, y=2, z=2
> > foo: x=2, y=2, z=2
> >
> > Duncan Murdoch
> >
>
> ______________________________________________
> R-help using r-project.org mailing list -- To UNSUBSCRIBE and more, see
> https://stat.ethz.ch/mailman/listinfo/r-help
> PLEASE do read the posting guide
> http://www.R-project.org/posting-guide.html
> and provide commented, minimal, self-contained, reproducible code.
>

	[[alternative HTML version deleted]]




More information about the R-help mailing list