# Mutation, Continued --- CS 135 // 2021-05-04 ## Administriva - Assignment 3 due today - No assignment 4 - Instead, concepts from the last week will be folded into the final exam + Will be released over the weekend + Will be due at 5pm on Friday - Last 15-minutes of class today will be devoted to course evaluations # Questions ## ...about anything? # Mutation ## What is mutation? - We created a language **without mutation** + No side effects are possible - What do we mean by mutation though? + For example, consider the Haskell code: ```haskell double x = x + x power x n | n == 0 = 1 | otherwise = power (double x) (n-1) ``` + Since `x` is changing with the function calls, isn't this a form of mutability? + Each function call has a unique `x` that cannot be affected by any other function call ## Mutation in Java - Consider the following Java snippets: ```java[1-3] x = 5; obj.x = 5; x = 5; ``` - What's the difference between these? - It depends on context ## First Case - The first snippet is mutating a local variable ```java[1-6|4] public static void main(String[] args) { int x = 0; System.out.println(x); x = 5; System.out.println(x); } ``` - This mutates the variable `x` in the lexical scope of the main method ## Second Case - The second snippet is mutating an instance variable ```java[1-6|4] public static void main(String[] args) { Point obj = new Point(0, 0); System.out.println(obj.x); obj.x = 5; System.out.println(obj.x); } ``` - Here the variable `obj` doesn't change, but the **object** that it references is mutated ## Third Case - The third snippet also mutates an instance variable ```java[1-10|8] public class Point { public Point(int x, int y) { this.x = x; this.y = y; } public void changeX() { x = 5; } } ``` ## Types of Mutation - Overall we see two types of mutation: 1. Mutating a **variable** 2. Mutating an **object** ## Mutability and Closures - With mutable variables, we need to be careful! - Consider the following Python code: ```py fs = [] for i in range(10): f = lambda x: return i fs.append(f) result = [f(0) for f in fs] ``` - What will be in `result`? ## Maps and Closures - Now consider the following Haskell code: ```haskell fs = map (\i -> (\x -> i)) [0..9] result = [f 0 | f <- fs] ``` - What will be in `result` now? # Mutable Boxes ## Boxed Values - We will start by adding a simple mutable structure called a **box** + A box is essentially an object with a single instance variable - Operations on boxes typically include: + **Creation:** `(box 5)` + **Unboxing:** `(unbox b)` for some box `b` + **Mutation:** `(set-box! b 10)` mutates the value in `b` to be `10` ## Box Analogy in Python - The `box` type will allow us to do things like this: ```py[1-7|5|7|2] def mutate(b): b[0] = 10 # akin to (set-box! b 10) def main(): b = [5] # akin to (let (b (box 5)) ...) mutate(b) print(b[0]) # akin to (unbox b) ``` - Note that `mutate` has a **side effect** which is impossible to do without a mutable object ## Updating `Expr` - Our `Expr` type becomes: ```haskell[7-9|10] data Expr = Plus Expr Expr | Mult Expr Expr | Num Int | FnDef Identifier Expr | FnApp Expr Expr | Id Identifier | Box Expr | Unbox Expr | SetBox Expr Expr | Seq Expr Expr deriving Show ``` - `Seq` is akin to the `(begin exp1 exp2)` in Racket + Executes `exp1`, then `exp2` (returns `exp2` result) ## Updating `Value` - Our `Value` type becomes the following: ```haskell[3] data Value = NumV Int | FnV Identifier Expr Env | BoxV Value deriving Show ``` ## Updating `interp` - We can now add some cases to `interp` : ```haskell Box x -> BoxV (interp env x) ``` ```haskell Unbox x -> case interp env x of BoxV val -> val _ -> error "*** not a box" ``` - How do we implement `SetBox` and `Seq x y`? # Introducing the Store ## The Store - To simulate mutability, we need a way to keep track of changes in successive calls to `interp` - This is commonly done by separating the environment into two parts: 1. Associating identifiers with **locations** 2. Associating locations with values - This is somewhat analogous to how most languages have **stack** memory and **heap** memory ## Updating Datatypes - We will do the separation in the following way: ```haskell type Location = Int type Env = [(Identifier,Location)] type Store = [(Location,Value)] ``` - We can lookup locations in `Env` with ```haskell lookupEnv :: Identifier -> Env -> Location lookupEnv x [] = error ("*** unbound variable ") lookupEnv x (b:bs) | x == fst b = snd b | otherwise = lookupEnv x bs ``` ## Operations on `Store` - We will maintain that each `Location` in the store is unique and maintain that the largest location is first ```haskell lookupSt :: Location -> Store -> Value lookupSt x [] = error ("*** location doesn't exist ") lookupSt x (b:bs) | x == fst b = snd b | otherwise = lookupSt x bs ``` ```haskell malloc :: Store -> Location malloc [] = 0 malloc (b:_) = fst b + 1 ``` ## Updating `Value` ... Again - Our `BoxV` should have a location instead a value ```haskell[3] data Value = NumV Int | FnV Identifier Expr Env | BoxV Location deriving Show ``` - We can now change a box's value by updating the `Location` in the store ## Updating `interp` ... Again - Next, we need to modify `interp`'s signature: ```haskell interp :: Env -> Store -> Expr -> Result ``` - The `Result` is the following: ```haskell type Result = (Value, Store) ``` - This allows us to return both the `Value` of and an expression as well as its effects on the `Store` ## Updating `interp` ... Again ```haskell[2-3|4|5-6] interp :: Env -> Store -> Expr -> Result interp env st exp = case exp of Num n -> (NumV n, st) FnDef param body -> (FnV param body env, st) Seq x y -> let (x',st') = interp env st x in interp env st' y ``` - Note that we interpret `x` first and then pass the updated store when interpreting `y` ## Updating `interp` ... Again ```haskell[1-3|5-7] Plus x y -> let (x',st') = interp env st x (y',st'') = interp env st y in (binOp (+) x' y', st'') Mult x y -> let (x',st') = interp env st x (y',st'') = interp env st y in (binOp (*) x' y', st'') ``` - Note that we imposed an ordering of left-to-right + Side effects in `x` will affect `y` ## Updating `interp` ... Again ```haskell Box x -> let (val, st') = interp env st x loc = malloc st st'' = (loc,val):st' in (BoxV loc, st'') ``` - Need to allocate another `Location` for the `Box` and add it to the store ## Updating `interp` ... Again ```haskell Unbox x -> case interp env st x of (BoxV loc, st') -> (lookupSt loc st, st') _ -> error "*** not a box" ``` - Need to lookup the value associated with the box in the store after evaluating `x` ## Updating `interp` ... Again ```haskell SetBox b v -> case interp env st b of (BoxV loc, st') -> let (v', st'') = interp env st' v st''' = updateSt loc v' st'' in (v', st''') _ -> error "*** not a box" ``` - Looks up the location of `b` and updates it in the store with the new value `v` ## Updating `interp` ... Again ```haskell FnApp f arg -> case interp env st f of (FnV param body closure, st') -> let (arg',st'') = interp env st arg loc = malloc st env' = (param, loc):closure st''' = (loc,arg'):st'' in interp env' st''' body _ -> error "*** not a function" ``` # Course Evals