0

Hello people of the internet,

I am working on a project for a class and cannot solve this bug for the life of me. The project is to write a Scheme interpreter in Python. Much of the code was given to me and I had to fill in the rest. I am on the last portion where we are implementing tail call optimization. When I implemented it, it worked for most test cases, but all cases using "and" and "or" operations suddenly stopped working.

After some investigation, I found out why: As my program works through the operands to the and/or expression, it returns an Unevaluated object to be evaluated later.

def optimize_tail_calls(original_scheme_eval):
    """Return a properly tail recursive version of an eval function."""
    def optimized_eval(expr, env, tail=False):
        """Evaluate Scheme expression EXPR in Frame ENV. If TAIL,
        return an Unevaluated containing an expression for further evaluation.
        """
        if tail and not scheme_symbolp(expr) and not self_evaluating(expr):
            return Unevaluated(expr, env)
        
        result = Unevaluated(expr, env)
        # BEGIN PROBLEM EC
        while isinstance(result, Unevaluated):
            result = original_scheme_eval(result.expr, result.env)
        return result
        # END PROBLEM EC
    return optimized_eval

This^ is the code that optimizes tail calls.

def do_and_form(expressions, env):
    """Evaluate a (short-circuited) and form.

    >>> env = create_global_frame()
    >>> do_and_form(read_line("(#f (print 1))"), env) # evaluating (and #f (print 1))
    False
    >>> # evaluating (and (print 1) (print 2) (print 4) 3 #f)
    >>> do_and_form(read_line("((print 1) (print 2) (print 3) (print 4) 3 #f)"), env)
    1
    2
    3
    4
    False
    """
    # BEGIN PROBLEM 12
    current = expressions
    if current is nil:
        return True
    while not (current is nil):
        val = scheme_eval(current.first, env, True) #Optimize tail call
        if is_scheme_false(val):
            return False
        if current.rest is nil:
            return val
        else:
            current = current.rest
    # END PROBLEM 12

This^ is the code that executes "and" expressions.

def do_or_form(expressions, env):
    current = expressions
    if current is nil:
        return False
    while not (current is nil):
        val = scheme_eval(current.first, env, True) #Optimize tail call
        if is_scheme_true(val):
            return val
        if current.rest is nil:
            return False
        else:
            current = current.rest

Similar, this^ is the code for "or" expressions.

As this code is, the issue I'm running into is that the expressions aren't being evaluated since they're staying Unevaluated objects. Take this test case for example:

scm> (define x 0)
x
scm> (and (define x (+ x 1))
....      (define x (+ x 10))
....      (define x (+ x 100))
....      (define x (+ x 1000)))
x
scm> x
1000

# Error: expected
#     1111
# but got
#     1000

You can see that the first 3 define functions became Unevaluated objects and only the last one was returned and evaluated, leaving x to be 1000 instead of 1111. I tried remedying this by adding the following to "and" and "or" forms:

if isinstance(val, Unevaluated):
    val = scheme_eval(val.expr, val.env)

This^ did solve my issue with those cases, but now when I am given test cases that attempt to recurse over 1000 times, (the limit in Python) it throws a maximum recursion depth error, meaning this doesn't implement tail recursion correctly. Here is the test case they give me for that:

scm> (define (sum n total)
....   (or (and (zero? n) total)
....       (add n (+ n total))))
sum
scm> (define add (lambda (x+1 y) (sum (- x+1 1) y)))
add
scm> (sum 1001 0)
0

# Error: expected
#     501501

Also, as a final note, most of the variable names are pretty self-explanatory, but I would be happy to clarify what anything means. A previous, modified version of this exact problem can be found here: https://github.com/melissakly/CS61A/blob/master/scheme3/scheme3.py

I know this is a long problem, but I would greatly appreciate anyone who could point me in the right direction!

WyWyGuy
  • 3
  • 3
  • Welcome, WyWyGuy! Did you try running the code in a Python debugger such as PyCharm? There, you can set breakpoints, step through the code, examine variables, and such. – Jerry101 Nov 29 '22 at 05:23
  • @Jerry101 I have tried to, but because the code is spread across multiple files and we are using okpy to test our code, it's near impossible to step through scheme input while it runs python – WyWyGuy Nov 29 '22 at 05:45
  • Where did you add that the the `and` and `or` forms? Only the last subexpression is in tail position. – molbdnilo Nov 29 '22 at 13:50
  • @molbdnilo I initially tried it after the ```val``` assignment but that didn't work. I also tried this: ``` current = expressions if current is nil: return True while not (current is nil): if current.rest is nil: return scheme_eval(current.first, env, True) #Optimize tail call val = scheme_eval(current.first, env) if is_scheme_false(val): return False else: current = current.rest``` This makes it so only the last element in the ```and``` form is tail call optimized but it didn't work either. – WyWyGuy Nov 29 '22 at 14:25
  • I'm not entirely sure about your plan, but given your interface I would expect to see `val = scheme_eval(current.first, env, current.rest is nil)`. – molbdnilo Nov 29 '22 at 14:51
  • @molbdnilo THANK YOU SO MUCH!!! I NEVER THOUGHT OF THAT BUT THAT WORKED PERFECTLY!!! I AM SO GRATEFUL, THANK YOU!!! – WyWyGuy Nov 29 '22 at 15:20

0 Answers0