""" CLOSURES PROGRAMMING ASSIGNMENT, PART I Python has full support for untyped lambda terms. It also has support for creating closures without using the "lambda" syntax. """ def K(x): def inner(y): return x return inner #K print(K(1)(3)) # prints 1 print(K(lambda x:x)(1)(3)) # prints 3 """ The K combinator returns a closure equivalent to lambda y:x. A proper closure starts with a lambda term that contains FREE variables. A closure is a pair consisting of that lambda term and a set of bindings (the "environment") for the free variables of that term. We know that pure lambda calculus does not allow mutation: indeed there's no mention of memory in lambda calculus. But we need RAM in real computers to compute just about anything. We can hide the memory operations using recursion and other abstractions, but what are the consequences if we allowed a program to directly change the contents of memory? What does allowing `x=x+1` do to our programming language, besides allowing us to write for/while loops? It turns out that changing the value of a local (bound) variable is usually harmless. The fun starts when we change the value of FREE variables inside a closure. We can further distinguish between two types of closures: those that never change their free variables, and those that do. We can call them "mutable" or "immutable". For immutable closures, we don't really need to keep the free variables stored in memory - just copy over them as constants. For example, (lambda x.lambda y.x)3 just becomes lambda y.3. However, if we're to change x inside the closure, then we need to associate a memory location with x, and that memory location must be "alive" as long as the lambda term that refers to it. Fortunately languages like Python has a magical memory fairy that handles all this memory allocation stuff for you. For the first part of this assignment, we are going to explore closures in Python, which is representative of a large number of languages. In the next part of the assignment, we will leave fairyland ... Some languages (Java) only allow immutable closures in lambda terms, but most modern languages also support mutable closures. Python3 supports mutable closures with the `nonlocal` keyword (as opposed to just `global`). A function that declares a variable to be nonlocal has mutable access to a free variable that's declared in an outer scope. We can use this feature to write functions that create closures (return closures). """ def make_accumulator(): x = 0 # variable local to make_accumulator def inner(dx): nonlocal x # this keyword signals the formation of a mutable closure x = x + dx return x # end of inner function return inner # body of outer function (returns inner closure) #make_accumulator a1 = make_accumulator() a2 = make_accumulator() print(a1(2)) # prints 2 print(a1(2)) # prints 4 print(a1(3)) # prints 7 print(a2(2)) # prints 2 - a2 is a different closure. """Both a1 and a2 are "instances" of the inner function. They point to the same source code (lambda term) but not the same environment: they're different closures. Each carries with it a different "instance" of the variable x. Please note that x is not a global variable: it's locally created each time make_accumulator is called. A closure behaves like an "object": calling a "method" on one object is not the same as calling it on another object. We could've written the same program in a variety of languages including javascript, perl, ruby, etc. These languages were all influenced, directly or indirectly, by Scheme, which is the language used in your reading assignment: "Modularity, Objects and State", part of a classic textbook. YOUR ASSIGNMENT, PART I: ******************************** Do the exercises from the book exert "Modularity, Objects, and State", numbers 3.1-3.4 (between pages 297-305), and exercise 3.7 on page 319. Do the exercises in Python3 instead of Scheme. ***For this assignment you are not allowed to use classes or any data structures (lists, association arrays/hashmaps), either built-in or user defined. YOU MUST USE CLOSURES EXCLUSIVELY as demonstrated in the sample code here. The "bank account" example used in the reading is reproduced in Python3 below, with the balance_transfer "method" my own addition. """ # Bank accounts using closures in Python def newaccount(name): balance = 0 def inquiry(): return balance # `nonlocal` not needed for immutable closure def deposit(n): nonlocal balance if n>0: balance += n def withdraw(n): nonlocal balance if n>0 and balance-n>=0: balance -= n def balance_transfer(otheraccount): nonlocal balance otherbalance = otheraccount("inquiry") otheraccount("withdraw")(otherbalance) balance += otherbalance def public_interface(request): if request=="inquiry": return inquiry() # function called directly elif request=="deposit": return deposit # function itself returned elif request=="withdraw": return withdraw elif request=="balance transfer": return balance_transfer else: print("Invalid request",request) return None #public_interface return public_interface # body of newaccount #newaccount myaccount = newaccount("Liang") youraccount = newaccount("Student") myaccount("deposit")(100) # kind of like myaccount.deposit(100), youraccount("deposit")(200) youraccount("withdraw")(40) myaccount("deposit")(150) myaccount("withdraw")(50) myaccount("balance transfer")(youraccount) print("my balance is ",myaccount("inquiry")) # my balance is 360 print("your balance is",youraccount("inquiry")) # your balance is 0 # to read a string from stdin in python: x = input("enter a number: ") # to convert string "12" to number 12: xnum = int(x)