Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How should tail call recursion be handled? #60

Open
dgkf opened this issue Oct 6, 2023 · 1 comment
Open

How should tail call recursion be handled? #60

dgkf opened this issue Oct 6, 2023 · 1 comment

Comments

@dgkf
Copy link
Owner

dgkf commented Oct 6, 2023

Inspired by recent advances in the R language, I went ahead and implemented a form of tail call recursion. However, it necessarily executes in a "non-standard" way, greedily evaluating arguments to recursive calls.

This means that expectations of R would apply to most calls, but might be unexpected for tail-recursive calls.

Typical Evaluation

f <- function(n, result) {
  if (n > 0) { 
    cat(n, "\n")
    f(n - 1, result)
  } else {
    result
  }
}

f(3, stop("Done"))
#> 3
#> 2
#> 1
#> Error: Done

Tail-Recursive Evaluation

f <- function(n, result) {
 if (n > 0) { 
   cat(n, "\n")
   f(n - 1, result)
 } else {
   result
 }
}

f(3, stop("Done"))
#> Error: Done

This is because result is necessarily greedily evaluated. Recursive calls will not work with promises dependent on the parent frame or non-standard evaluation. The "right" approach here is a bit unclear, so I'm going to wait to see how the R version feels with all of R's non-standard eval tricks to see if I can learn something from the design.

@dgkf
Copy link
Owner Author

dgkf commented Mar 2, 2024

Was mulling over this today and I think there happy middle ground where tail call optimization only happens if all arguments are evaluated (equivalent to forceed in R).

no_tail_call_opt <- function(n, msg) {
  if (n > 0) {
    no_tail_call_opt(n - 1, msg)
  } else {
    msg
  }
}

tail_call_opt <- function(n, msg) {
  if (n > 0) {
    force(msg)
    tail_call_opt(n - 1, msg)
  } else {
    msg
  }
}

When a recursive call overflows the call stack, an error message could even suggest the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant