From 7ff5271b9d0942200db464da0b9493130187a223 Mon Sep 17 00:00:00 2001 From: Thomas Mailund Date: Fri, 9 Feb 2018 14:01:16 +0100 Subject: [PATCH] More progress towards tail-recursive cases Progress on #5 and mailund/pmatch#19. I need a non-local `next` when calling recursively in `with` expressions to make this work. --- DESCRIPTION | 2 +- NEWS.md | 5 ++++ R/loop-transformation.R | 29 +++++++++++++++++++++++ tests/testthat/test-loop-transformation.R | 16 +++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index f14b9d0..53b7dcb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: tailr -Version: 0.0.0.9001 +Version: 0.0.0.9002 Title: Automatic Tail Recursion Optimisation Description: This package implements meta-programming functions for automatically translating recursive functions into looping functions or trampolines. diff --git a/NEWS.md b/NEWS.md index c2cea1b..0bd911b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# tailr 0.0.0.9002 + +* Handle `with` expressions. +* Make explicit that we cannot handle `eval`. + # tailr 0.0.0.9001 * Added framework for user-defined transformations. diff --git a/R/loop-transformation.R b/R/loop-transformation.R index ff7c02d..11f48a2 100644 --- a/R/loop-transformation.R +++ b/R/loop-transformation.R @@ -24,6 +24,23 @@ can_call_be_transformed <- function(call_name, call_arguments, } }, + # Eval is really just evaluation of an expression in the calling scope, + # so we shouldn't consider those function calls either... I'm not sure how to + # handle them when it comes to what they return, though, since it depends + # on the expression they will evaluate + "eval" = { + warning("We can't yet handle eval expressions.") + cc(FALSE) + }, + + # With expressions are a bit like eval, I guess... don't consider them + # function calls. + "with" = { + for (arg in call_arguments) { + can_transform_rec(arg, fun_name, fun_call_allowed, cc) + } + }, + # Selection "if" = { can_transform_rec(call_arguments[[1]], fun_name, fun_call_allowed, cc) @@ -201,6 +218,18 @@ make_returns_explicit_call <- function(call_expr, in_function_parameter) { call_expr[[n]] <- make_returns_explicit(call_expr[[n]], in_function_parameter) }, + # Not sure how to handle eval, exactly... + # The problem here is that I need to return the expression if it is not a recursive call + # but not if it is... + "eval" = { + stop("FIXME") + }, + + # With should just be left alone and we can deal with the expression it evaluates + "with" = { + call_expr[[3]] <- make_returns_explicit(call_expr[[3]], in_function_parameter) + }, + # For all other calls we transform the arguments inside a call context. { for (i in seq_along(call_args)) { diff --git a/tests/testthat/test-loop-transformation.R b/tests/testthat/test-loop-transformation.R index f7ebbd7..a2a7f37 100644 --- a/tests/testthat/test-loop-transformation.R +++ b/tests/testthat/test-loop-transformation.R @@ -97,3 +97,19 @@ test_that("we cannot transform a non-tail-recursive function", { "Could not build .*" ) }) + +test_that("we can handle `with` expressions", { + f <- function(x) { + if (x < 0) x + else with(list(y = -1), f(x + y)) + } + + expect_true(can_loop_transform(f)) + transformed_f <- loop_transform(f) + + skip("this doesn't work because we cannot use `next` inside `with`.") + for (x in 1:5) { + expect_equal(f(x), transformed_f(x)) + } + +})