20

"R passes promises, not values. The promise is forced when it is first evaluated, not when it is passed.", see this answer by G. Grothendieck. Also see this question referring to Hadley's book.

In simple examples such as

> funs <- lapply(1:10, function(i) function() print(i))
> funs[[1]]()
[1] 10
> funs[[2]]()
[1] 10

it is possible to take such unintuitive behaviour into account.

However, I find myself frequently falling into this trap during daily development. I follow a rather functional programming style, which means that I often have a function A returning a function B, where B is in some way depending on the parameters with which A was called. The dependency is not as easy to see as in the above example, since calculations are complex and there are multiple parameters.

Overlooking such an issue leads to difficult to debug problems, since all calculations run smoothly - except that the result is incorrect. Only an explicit validation of the results reveals the problem.

What comes on top is that even if I have noticed such a problem, I am never really sure which variables I need to force and which I don't.

How can I make sure not to fall into this trap? Are there any programming patterns that prevent this or that at least make sure that I notice that there is a problem?

Community
  • 1
  • 1
Eike P.
  • 3,333
  • 1
  • 27
  • 38
  • 1
    seems like this may be a good opportunity to re-evaluate your programming style if you're forcing the language you're working in to do something it's not inherently set up to do... – Chase Mar 16 '15 at 18:52
  • 11
    ... which is why I was asking for patterns that circumvent this problem. ;-) – Eike P. Mar 16 '15 at 18:54
  • This question is still of historical interest, but readers today may be interested to know that as of R 3.4.1 (maybe as early as 3.2.0) the example in OPs post causes funs[[5]]() to result in `5`. – russellpierce Nov 03 '17 at 00:59
  • 1
    @russellpierce That is true (and was indeed changed in R 3.2.0), but it does not render this question obsolete. The only thing changed (as far as I know) is that higher-order functions in base R, such as `lapply` and `Reduce` now automatically `force` their arguments. The lazy evaluation principle still remains, hence this does not change anything for custom functions. In line with the answers provided below it appears that the proper solution is to either always `force` function arguments in custom higher-order functions, or to employ the provided machinery, such as `lapply` or `Curry`. – Eike P. Nov 03 '17 at 11:26
  • @jim thanks for the context. So, what would a repex of the issue look like today? – russellpierce Nov 05 '17 at 15:35
  • 1
    @russellpierce the for-loop equivalent to `lapply` will still demonstrate the issue in R 4.1.3. – Mikko Marttila Apr 04 '22 at 09:06

4 Answers4

12

You are creating functions with implicit parameters, which isn't necessarily best practice. In your example, the implicit parameter is i. Another way to rework it would be:

library(functional)
myprint <- function(x) print(x)
funs <- lapply(1:10, function(i) Curry(myprint, i))
funs[[1]]()
# [1] 1
funs[[2]]()
# [1] 2

Here, we explicitly specify the parameters to the function by using Curry. Note we could have curried print directly but didn't here for illustrative purposes.

Curry creates a new version of the function with parameters pre-specified. This makes the parameter specification explicit and avoids the potential issues you are running into because Curry forces evaluations (there is a version that doesn't, but it wouldn't help here).

Another option is to capture the entire environment of the parent function, copy it, and make it the parent env of your new function:

funs2 <- lapply(
  1:10, function(i) {
    fun.res <- function() print(i)
    environment(fun.res) <- list2env(as.list(environment()))  # force parent env copy
    fun.res
  }
)
funs2[[1]]()
# [1] 1
funs2[[2]]()
# [1] 2

but I don't recommend this since you will be potentially copying a whole bunch of variables you may not even need. Worse, this gets a lot more complicated if you have nested layers of functions that create functions. The only benefit of this approach is that you can continue your implicit parameter specification, but again, that seems like bad practice to me.

BrodieG
  • 51,669
  • 9
  • 93
  • 146
  • Thanks, I think this was just the hint that I needed (regarding implicit parameter dependencies)! Doesn't this essentially boil down to using closures vs. extracting everything in another function? I think, I was starting this style initially because of Hadley's promotion of closures (http://adv-r.had.co.nz/Functional-programming.html). – Eike P. Mar 16 '15 at 19:11
  • 1
    Regarding the specific implementation, is returning `Curry(myprint, i)` equivalent to `force(i); function() myprint(i)`? I am a little bit hesitant to make such an integral part of my programming style dependent on a non-standard package. Consider e.g. readability for colleagues not knowledgeable about functional programming. – Eike P. Mar 16 '15 at 19:17
  • 1
    The most common use of closures is to create functions with a persistent but modifiable state. You can use them to specify implicit parameters for functions, but again, that doesn't seem like good practice. – BrodieG Mar 16 '15 at 19:28
  • @jhin, re equivalence, yes, to the extent 1 + 1 and 3 - 1 are equivalent. I still prefer the more explicit approach. Note though that function calls in R are expensive so this type of pattern is not very common. – BrodieG Mar 16 '15 at 19:36
7

As others pointed out, this might not be the best style of programming in R. But, one simple option is to just get into the habit of forcing everything. If you do this, realize you don't need to actually call force, just evaluating the symbol will do it. To make it less ugly, you could make it a practice to start functions like this:

myfun<-function(x,y,z){
   x;y;z;
   ## code
}
mrip
  • 14,913
  • 4
  • 40
  • 58
  • 3
    The verbosity, or "ugliness", of `force` is its very purpose: it makes _intent_ explicit—strict evaluation is being imposed. – egnha Mar 05 '17 at 06:02
5

There is some work in progress to improve R's higher order functions like the apply functions, Reduce, and such in handling situations like these. Whether this makes into R 3.2.0 to be released in a few weeks depend on how disruptive the changes turn out to be. Should become clear in a week or so.

Luke Tierney
  • 1,025
  • 5
  • 5
  • Did +1 to encourage modification once more information is available. ;) – Eike P. Mar 19 '15 at 10:43
  • Some relevant changes have indeed been incorporated in R 3.2.0. I assume this is everything you were referring to? Or are more changes planned? – Eike P. Apr 22 '15 at 02:37
2

R has a function that helps safeguard against lazy evaluation, in situations like closure creation: forceAndCall().

From the online R help documentation:

forceAndCall is intended to help defining higher order functions like apply to behave more reasonably when the result returned by the function applied is a closure that captured its arguments.

egnha
  • 1,157
  • 14
  • 22