[R] Continuation-parsing / trampoline / infinite recursion problem

Thomas Mailund mailund at birc.au.dk
Thu Aug 11 06:39:14 CEST 2016

```Yes, I am aware of this situation and I agree that it is better to force f. I was simply trying to figure out why it was necessary in this particular program where the only repeated assignment anywhere in the code is in trampoline, in a scope none of the thunks can see.

What ever my problem was, it was not that the binding of f changes. The code works correctly if I output info with cat, so unless cat could affect this it can't be.

I still don't quite understand that but I suspect it is ... that is doing something I don't understand. Either that or my understanding of in which scope lazy parameters get evaluated is completely wrong.

Anyway, that being said, you are of course right that it is better to force f in my actual program, and I will.

Thanks

On 10 August 2016 at 22:22:32, Duncan Murdoch (murdoch.duncan at gmail.com<mailto:murdoch.duncan at gmail.com>) wrote:

On 10/08/2016 2:39 PM, Thomas Mailund wrote:

Ok, I think maybe I am beginning to see what is going wrong...

Explicitly remembering the thunk parameters in a list works fine, as far as I can see.

make_thunk <- function(f, ...) {
remembered <- list(...)
function(...) do.call(f, as.list(remembered))
}

Where that will fail is in a situation like this:

thunklist <- list(thunk_factorial, thunk_somethingelse)
for (i in seq_along(thunklist))
thunklist[[i]] <- make_thunk(thunklist[[i]])

The problem is that the first time thunklist[[1]] is evaluated, it will
call the function thunklist[[2]] (or something else if i has been
modified in the meantime), and things will go bad. That's why it's
important to force both f and ... in make_thunk.

Duncan Murdoch

thunk_factorial <- function(n, continuation = identity) {
if (n == 1) {
continuation(1)
} else {
new_continuation <- function(result) {
make_thunk(continuation, n * result)
}
make_thunk(thunk_factorial, n - 1, new_continuation)
}
}

trampoline <- function(thunk) {
while (is.function(thunk)) thunk <- thunk()
thunk
}

trampoline(thunk_factorial(100))

But if I delay the evaluation of the parameters to thunk I get an error

make_thunk <- function(f, ...) {
remembered <- eval(substitute(alist(...))) # not evaluating parameters yet
function(...) do.call(f, as.list(remembered))
}

thunk_factorial <- function(n, continuation = identity) {
if (n == 1) {
continuation(1)
} else {
new_continuation <- function(result) {
make_thunk(continuation, n * result)
}
make_thunk(thunk_factorial, n - 1, new_continuation)
}
}

trampoline(thunk_factorial(100))

Running this version I am told, when applying the function, that it doesn’t see variable `n`.

As far as I can see, the thunk remembers the parameters just fine. At least this gives me the parameters I made it remember

x <- 1
f <- make_thunk(list, a = 1 * x, b = 2 * x)
g <- make_thunk(list, c = 3 * x)
f()
g()

Here I just get the parameters back in a list because the wrapped function is `list`. (The reason I have `x` as a global variable and use it in the arguments is so I get call objects that needs to be evaluated lazily instead of just values).

These values contain the expressions I gave the `make_thunk` function, of course, and they are not evaluated. So in the factorial function the missing `n` is because I give it the expression `n - 1` that it of course cannot evaluate in the thunk.

So I cannot really delay evaluation.

Does this sound roughly correct?

Now why I can still get it to work when I call `cat` remains a mystery…

Cheers
Thomas

On 10 August 2016 at 19:12:41, Thomas Mailund (mailund at birc.au.dk(mailto:mailund at birc.au.dk)) wrote:

That did the trick!

I was so focused on not evaluating the continuation that I completely forgot that the thunk could hold an unevaluated value… now it seems to be working for all the various implementations I have been playing around with.

I think I still need to wrap my head around *why* the forced evaluation is necessary there, but I will figure that out when my tired brain has had a little rest.

Thanks a lot!

Thomas

On 10 Aug 2016, at 19:04, Duncan Murdoch wrote:

On 10/08/2016 12:53 PM, Thomas Mailund wrote:
On 10 Aug 2016, at 13:56, Thomas Mailund wrote:

make_thunk <- function(f, ...) f(...)

Doh! It is of course this one:

make_thunk <- function(f, ...) function() f(…)

It just binds a function call into a thunk so I can delay its evaluation.

I haven't looked closely at the full set of functions, but this comment:

force(continuation) # if I remove this line I get an error

makes it sound as though you're being caught by lazy evaluation. The "make_thunk" doesn't appear to evaluate ..., so its value can change between the time you make the thunk and the time you evaluate it. I think you could force the evaluation within make_thunk by changing it to

make_thunk <- function(f, ...) { list(...); function() f(…) }

and then would be able to skip the force() in your thunk_factorial function.

Duncan Murdoch

______________________________________________
R-help at r-project.org mailing list -- To UNSUBSCRIBE and more, see
https://stat.ethz.ch/mailman/listinfo/r-help
and provide commented, minimal, self-contained, reproducible code.
______________________________________________
R-help at r-project.org mailing list -- To UNSUBSCRIBE and more, see
https://stat.ethz.ch/mailman/listinfo/r-help