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

Duncan Murdoch
Wed, 10 Aug 2016 20:22:32 -0000

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
>> PLEASE do read the posting guide
http://www.R-project.org/posting-guide.html
>> 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
> PLEASE do read the posting guide
http://www.R-project.org/posting-guide.html
> and provide commented, minimal, self-contained, reproducible code.
>