2

A colleague and I are doing a free R course, although I believe this is a more general lazy evaluation issue, and have found a scenario that we have discussed briefly and I'd like to find out the answer from a wider community.

The scenario is as follows (pseudo code):

wrapper => function(thing)
{
    print => function()
    {
        write(thing)
    }
}

v = createThing(1, 2, 3)
w = wrapper(v)
v = createThing(4, 5, 6)
w.print()
// Will print 4, 5, 6 thing.
v = create(7, 8, 9)
w.print()

// Will print 4, 5, 6 because "thing" has now been evaluated.

Another similar situation is as follows:

// Using the same function as above
v = createThing(1, 2, 3)
v = wrapper(v)
w.print()
// The wrapper function incestuously includes itself.

Now I understand why this happens but where my colleague and I differ is on what should happen.

My colleague's view is that this is a bug and the evaluation of the passed in argument should be forced at the point it is passed in so that the returned "w" function is fixed.

My view is that I would prefer his option myself, but that I realise that the situation we are encountering is down to lazy evaluation and this is just how it works and is more a quirk than a bug. I am not actually sure of what would be expected, hence the reason I am asking this question. I think that function comments could express what will happen, or leave it to be very lazy, and if the coder using the function wants the argument evaluated then they can force it before passing it in.

So, when working with lazy evaulation, what is the practice for the time to evaluate an argument passed, and stored, inside a function?

1

While I'm not familiar with R, based on the manual it seems to be due to the imperative nature of R combined the way it implements laziness. See 4.3.3 Argument evaluation and 4.3.4 Scope.

Let's look at the example you have:

wrapper =
  function(thing)
    function()
      print(thing)

First of all, notice that you can call

w = wrapper(v) // line A

without even defining v first. What happens is that the function wrapper captures a promise, which consists of 3 things:

  • a value (possibly absent),
  • an expression, and
  • a pointer to the environment.

Until something forces the evaluation of the promise, the promise remains unevaluated and thus its value is absent. Once the promise is forced, the value is filled in by the result of the evaluation and it will never be evaluated again. In other words, promises are evaluated at most once. (There is some complication if the evaluation fails, however.)

The key property is that a promise contains a pointer to the environment, not a copy of it! This means if the lexical environment changes later on, then the future value of the promise could also change!

Hence, if you enter the following after line A:

v = 10
w()

You will see that it will print the value 10.


I would agree that this is highly non-intuitive, but I don't believe it's a bug either. Even though I regularly work with Haskell (another lazy language), the language is designed in such a way that such things can't happen (partly due to the lack of impurity).

I think this is largely an artifact of the fact that promises hold pointers to their lexical environments, rather than a copy of it. This means that in R arguments are not truly "pass-by-value", nor are they "pass-by-reference", nor are they "pass-by-name"!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.