Evaluating str(evalInGlobal("square")) results in the string
<Procedure ['x'] / ['*', 'x', 'x']>because str() invokes the __str__ function defined in the Procedure class:
def __str__(self): return '<Procedure %s / %s>' % (str(self._params), str(self._body))
(define factorial (lambda (x) (if (= x 0) 1 (* x (factorial (- x 1))))))
def primitiveLessThanOrEqualTo (operands): checkOperands (operands, 2, "<=") return operands[0] <= operands[1]Then, we add it to the global environment:
def initializeGlobalEnvironment(): global globalEnvironment globalEnvironment = Environment(None) globalEnvironment.addVariable('true', True) globalEnvironment.addVariable('false', False) globalEnvironment.addVariable('+', primitivePlus) globalEnvironment.addVariable('-', primitiveMinus) globalEnvironment.addVariable('*', primitiveTimes) globalEnvironment.addVariable('=', primitiveEquals) globalEnvironment.addVariable('zero?', primitiveZero) globalEnvironment.addVariable('>', primitiveGreater) globalEnvironment.addVariable('<', primitiveLessThan) globalEnvironment.addVariable('<=', primitiveLessThanOrEqualTo)
class Cons: def __init__(self, left, right): self._left = left self._right = right def __str__(self): return "(%s . %s)" % (str(self._left), str(self._right)) def getFirst(self): return self._left def getSecond(self): return self._rightThen, we define the primitive procedures:
def primitiveCons (operands): checkOperands (operands, 2, "cons") res = Cons(operands[0], operands[1]) return res def primitiveCar(operands): checkOperands(operands, 1, "car") return operands[0].getFirst(); def primitiveCdr(operands): checkOperands(operands, 1, "cdr") return operands[0].getSecond();We add these to the global environment, by adding these three lines to initializeGlobalEnvironment:
globalEnvironment.addVariable('cons', primitiveCons) globalEnvironment.addVariable('car', primitiveCar) globalEnvironment.addVariable('cdr', primitiveCdr)Since we used the special name __str__ for our procedure that produces a string representation of a cons, the cons pairs will display correctly in the evalLoop with,
print str(meval (expr, globalEnvironment))
def primitiveIsNull (operands): checkOperands(operands, 1, "null?") return operands[0] == None(note that we cannot use ? in identifier names in Python, so need to use a different name).
To define null (which is not a procedure), we just add it to the global environment by adding these statements to initializeGlobalEnvironment:
globalEnvironment.addVariable('null', None) globalEnvironment.addVariable('null?', primitiveIsNull)
def primitiveList (operands): if (len(operands) == 0): return None else: return Cons(operands[0], primitiveList(operands[1:]))and add it to the global environment:
globalEnvironment.addVariable('list', primitiveList)With this definition, lists will just print out as nested cons pairs:
Charme> (list 1 2 3 4) (1 . (2 . (3 . (4 . None))))If we want them to print out as they do in DrRacket, we also need to modify the __str__ method of Cons to do something special when the cons pair is a list.
We define isList:
def isList(expr): if expr == None: return True elif isinstance(expr, Cons): return isList(expr.getSecond()) else: return FalseNote that this matches exactly our definition of the list datatype - either null, or a pair whose second part is a list!
Then, we modify the Cons __str__ method (using the new strAsList helper method:
class Cons: def __init__(self, left, right): self._left = left self._right = right def __str__(self): if isList(self): return "(%s)" % self.strAsList() return "(%s . %s)" % (str(self._left), str(self._right)) def strAsList(self): if self._right == None: return str(self._left) else: return str(self._left) + " " + self._right.strAsList() def getFirst(self): return self._left def getSecond(self): return self._right
def isConditional(expr): return isSpecialForm(expr, 'cond') def evalConditional(expr, env): assert isConditional(expr) if len(expr) <= 2: evalError ("Bad conditional expression: %s" % str(expr)) for clause in expr[1:]: if len(clause) != 2: evalError ("Bad conditional clause: %s" % str(clause)) predicate = clause[0] result = meval(predicate, env) if not result == False: return meval(clause[1], env) return NoneWe also need to add a clause for cond expressions to meval:
def meval(expr, env): if isPrimitive(expr): return evalPrimitive(expr) elif isIf(expr): return evalIf(expr, env) elif isConditional(expr): return evalConditional(expr, env) elif isDefinition(expr): evalDefinition(expr, env) elif isName(expr): return evalName(expr, env) elif isLambda(expr): return evalLambda(expr, env) elif isApplication(expr): return evalApplication(expr, env) else: error ('Unknown expression type: ' + str(expr))
The modified Procedure class adds the _memos instance variable, initializes it to the empty dictionary, and defines the procedure hasResult, storeResult, and getResult:
class Procedure: def __init__(self, params, body, env): self._params = params self._body = body self._env = env self._memos = { } def getParams(self): return self._params def getBody(self): return self._body def getEnvironment(self): return self._env def hasResult(self, operands): return self._memos.has_key(str(operands)) def storeResult(self, operands, result): self._memos[str(operands)] = result def getResult(self, operands): assert self.hasResult(str(operands)) return self._memos[str(operands)] def __str__(self): return "We use str(operands) as the key to our dictionary. Dictionary keys cannot be lists, so we need to turn the operands into a string. Since the operands are the values of the operand subexpressions, not the expressions, they have already been evaluated. Hence, we do not need to worry about names having different values in the operand values list." % (str(self._params), str(self._body))
We modify mapply to check if an application has a stored result. If it has a stored result, we use that as the result. Otherwise, we compute the result as before, and store it in the memoized results table.
def mapply(proc, operands): if (isPrimitiveProcedure(proc)): return proc(operands) elif isinstance(proc, Procedure): params = proc.getParams() if proc.hasResult(operands): return proc.getResult(operands) else: newenv = Environment(proc.getEnvironment()) if len(params) != len(operands): evalError ("Parameter length mismatch: %s given operands %s" % (proc.toString(), str(operands))) for i in range(0, len(params)): newenv.addVariable(params[i], operands[i]) result = meval(proc.getBody(), newenv) proc.storeResult(operands, result) return result else: evalError("Application of non-procedure: %s" % (proc))Now, evaluating (fibo 60) takes no time at all!
Charme> (define fibo (lambda (n) (if (= n 1) 1 (if (= n 2) 1 (+ (fibo (- n 1)) (fibo (- n 2))))))) Charme> (fibo 60) 1548008755920
You must be logged in to post a comment.