2
fa <- function(x){x+1}
fb <- function(x){x-1}

f1 <- function(x, y){f(x)^y}

f2 <- function(x, ab, y){
   if(ab == 'a'){
     f <- fa
   } else {
     f <- fb
   }
   f1(x, y)
}

f2(0, 'a', .5)
Error in f1(x, y) : could not find function "f"

The above doesn't work because f isn't defined in f1's environment.

What is a good way to make this work? That

  • Avoids passing everything in the global environment to f2's environment
  • Avoids having to redefine the function inside f2 (this would be a hassle and create opportunities for copy/pasting error)

Would it make sense to define some sort of "subglobal" environment, and put things that I want everybody to use in this environment, and then make every function be able to access things from "subglobal"? And then somehow make sure that subglobal is always a strict subset of global? If sensible, how would I do this?

alexis_laz
  • 12,884
  • 4
  • 27
  • 37
generic_user
  • 3,430
  • 3
  • 32
  • 56
  • A hacky fix to make everything work as is, can be to use `environment(f1) = environment()` at the line before calling `f1` inside `f2` since `f1` seaches for an `f` in `environment(f1)`. BTW, global `f1` won't be affected by this change in `f2`. Though I agree with Konrad in either passing explicit functions as arguments or adopt a class system if convenient. – alexis_laz Nov 23 '16 at 16:53

1 Answers1

8

The above doesn't work because fa and fb aren't defined in f2's environment.

No — they are. The error is completely different:

f1 <- function(x, y){f(x)^y}

Here you are using an undefined variable f, and that’s an error.

You have a different variable (confusingly also called f) inside f2. However, that’s irrelevant for f1 because f1 and f2 don’t share their local variables. If you want to pass f2’s f to f1 then you need to pass it as an argument.

Incidentally, the fact that your variable names are so similar makes this more complicated than necessary. If you change your code to the following, equivalent code, the problem becomes much clearer:

plus1 = function (x) x + 1
minus1 = function (x) x - 1

f_exp = function (x, y) g(x) ^ y

plusminus_exp = function (x, ab, y) {
    if (ab == 'a')
        f = plus1
    else
        f = minus1
    f_exp(x, y)
}

plusminus_exp(0, 'a', .5)
# Error in f_exp(x, y) : could not find function "g"

And here’s the fix:

f_exp = function (x, y, g) g(x) ^ y
f_exp(x, y, f)

Alternatively, if you are calling f_exp many times inside plusminus_exp, you can use some more advanced abstraction to create a function builder: a function that returns another function:

make_f_exp = function (f) {
    force(f)
    function (x, y)
        f(x) ^ y
}

plusminus_exp = function (x, ab, y) {
    f = if (ab == 'a') plus1 else minus1
    f_exp = make_f_exp(f)
    f_exp(x, y)
}

This makes f_exp pluggable but isolates the effect to avoid introducing a global modifiable state. This is pretty standard code for a functional programming language but much less common outside of functional languages and thus somewhat surprising for some people.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • Well, my real code has better names than my silly SO example to show my problem. But here is the thing: I want to find a way to not have to pass the argument `f` to `f1` (or `called_directly` if you prefer). I want to set the definition of `f` once, as the argument to a top-level function, and then have everything subsequently "just work" – generic_user Nov 23 '16 at 16:33
  • 1
    @generic_user That’s a mistake: what you intend to do creates a [mutable global state](http://softwareengineering.stackexchange.com/q/148108/2366), which means that you will be searching for weird bugs in this code for the rest of its existence. What you could do instead is create a function `make_f1` which takes a function as a parameter and returns a different function. See my updated answer. – Konrad Rudolph Nov 23 '16 at 16:43
  • @generic_user Incidentally, the “subglobal” environment you’re talking about sounds essentially like a class. If so, check out functional programming and in particular the R6 system for R. This should generally be avoided though, and reserved for cases where maintaining a large, mutable, stateful object is actually required. – Konrad Rudolph Nov 23 '16 at 16:47