# Definition of and examples of usage for function decorator trace
# Rhys Price Jones, November 2014

# *x is a way in Python to allow an unknown number of parameters
def trace(f):
    def newf(*x):
        print('Calling '+f.__name__+' with args '+str(x))
        value = f(*x)
        print('Returning '+str(value))
        return value
    return newf

# trace has an obvious use for debugging.
# It can also be used to model just how recursive functions work.

@trace
def factorial(n):
    if n<=1:
        return 1
    return n*factorial(n-1)
    
# In particular, we can discern an explanation for why the (bad) fibonacci program
# below is so ineffient

@trace
def fib(n):
    if n<=2:
        return 1
    return fib(n-1)+fib(n-2)

print('fib(5) = ',fib(5))