1 of 31

Reasoning about Effects

Immutability/Mutability, Ownership/Sharing, Algebraic Effects

2 of 31

Referential Transparency

Functions are functions. Same arguments equals same outputs.

let x = …��function f (x) {

return x * 2

}��let z = f(x)

let a = f(x)

Great for reasoning!

3 of 31

Optimization

Can replace redundant computations with single computation

let x = …��function f (x) {

return x * 2

}��let z = f(x)

let a = f(x)

let x = …��function f (x) {

return x * 2

}��let z = f(x)

let a = z

=

4 of 31

Concurrency/Parallelization

Can reorder computation arbitrarily (up to data dependencies)

function f (x) { … }

function g (x) { … }

�let x = …�

let z = f(x)

let a = g(x)

function f (x) { … }

function g (x) { … }

�let x = …��let a = g(x)

let z = f(x)

=

5 of 31

Modern languages are a blend

Mutability is unavoidable in modern systems

  • Coding: many programming patterns to organize program data that is a bit cumbersome to code functionally. Threading!
  • Data Structures: if we’d like to update a record or list, then what? Threading! Copying is a performance hit.
  • The outside world: modern programs interact with the world , such as filesystems, databases, distributed systems – other computers, or even physical actuation in robotics. We can’t copy the universe!��

6 of 31

Call-by-Value vs Call-by-Reference

Makes a lot more sense with data structures.���������Potential expensive copy on the left. Call by reference on the right.

let compute (p) =

p.x + 1��let v = {x : 1, y : …} in � compute(v)

let compute (p) =

p->x + 1��let v = {x : 1, y : …} in � compute(&v)

This is like C++ or Rust (and even C# and Java). We can be very explicit.

A subtlety to make sure we�don’t fully evaluate to a record

7 of 31

Reference Semantics

  • Now I can mutate a variable defined by a let.

&v

!v

8 of 31

Interfaces

  • What we are really looking for is not only the capability to mutate state, but also for interfaces that specify the contract that a function adheres to. “I won’t mutate this reference you passed in! I promise!”�
  • If we understand the contract, then we can understand the behavior to expect in certain situations.�
  • Types (and other static analyses) can provide interfaces that specify the behavior of the function w.r.t. to mutation and other effects.

9 of 31

Immutability/Mutability

If data is read-only, then I get referential transparency in that data that.

“Const” in languages like Typescript or C++ or frozen in Python. Alternatively, mut in Rust.

Extend type system with mutable locations (loc<t>) and immutable locations (const_loc<t>).

v := e only works if v is loc<t>

let x = …��let f (x : const_loc<int>) =

return !x * 2��let z = f(&x) in

!v works if v is loc<t> or const_loc<t>

&v only produces const_loc<t>

10 of 31

Optimization

Can replace redundant computations with single computation

let x = …��let f (x : const_loc<int>) =

return !x * 2��let z = f(&x) in

let a = f(&x) in

=

let x = …��let f (x : const_loc<int>) =

return !x * 2��let z = f(&x) in

let a = z in

11 of 31

Concurrency/Parallelization

Can reorder computation arbitrarily (up to data dependencies)

=

let f (x : const_loc<int>) = …

let g (x : const_loc<int>) = …

�let x = …�

let z = f(&x) in

let a = g(&x) in

...

let f (x : const_loc<int>) = …

let g (x : const_loc<int>) = …

�let x = …�

let a = g(&x) in

let z = f(&x) in

...

12 of 31

Sharing/Aliasing

let f (x) = … in

let g (x) = … in

�let x = ref 1 in

let y = x in�

let z = f(x) in

let a = g(y) in

...

=

let f (x) = … in

let g (x) = … in

�let x = ref 1 in

let y = x in�

let a = g(y) in

let z = f(x) in

...

13 of 31

Ownership

  • If I own something, no one else can modify it! Isolation of effects in a function from the data I own.

14 of 31

Ownership

let f (x : unique_loc<int>) = … in

let g (x : unique_loc<int>) = … in

�let x : unique_loc<int> = ref 1 in

let y : unique_loc<int> = x in�

let z = f(x) in

let a = g(y) in

...

let f (x : unique_loc<int>) = … in

let g (x : unique_loc<int>) = … in

�let x : unique_loc<int> = ref 1 in

let y : unique_loc<int> = ref 2 in�

let z = f(x) in

let a = g(y) in

...

unique_loc means that there is only one reference to the location: ownership! Borrowing is when I let some else have it for a bit, but I don’t retain a copy in the meantime: still unique.

15 of 31

Concurrency/Parallelization

Both f and g may write to x and y, respectively. But we can run the computations in arbitrary order, because they touch different data.�

=

let f (x : unique_loc<int>) = … in

let g (x : unique_loc<int>) = … in

�let x : unique_loc<int> = ref 1 in

let y : unique_loc<int> = ref 2 in�

let z = f(x) in

let a = g(y) in

...

let f (x : unique_loc<int>) = … in

let g (x : unique_loc<int>) = … in

�let x : unique_loc<int> = ref 1 in

let y : unique_loc<int> = ref 2 in�

let a = g(x) in

let z = f(y) in

...

16 of 31

Rust and Cyclone

  • The language we’ve built up with mutation (references, regions, etc) are all supported in Rust and Cyclone in different ways.

  • Both languages have been carefully designed long with expressive type systems to expose interfaces for Immutability/Mutability and Ownership/Sharing

17 of 31

Algebraic Effects

  • Everything we’ve seen so far provides implementation and reasoning strategies for mutable state in the form of program state/data. The most common effect one needs to think about.

18 of 31

Algebraic Effects

  • Everything we’ve seen so far provides implementation and reasoning strategies for mutable state in the form of program state/data. The most common effect one needs to think about.

  • But, what about the outside world?

19 of 31

I/O - Calc

let g = handler {

{

get_str() = resume (“now”)

} in

�let o2 = g(f(“Bob!”)) in …��

effect input

{

get_str()

}

let h = handler {

{

get_str() = resume (“there”)

} in

let f (x) = � “Hey ” + get_str() + “, ” + x in

�let o1 = h(f(“Bob!”)) in��// continued on next panel

20 of 31

I/O - Calc

effect input

{

get_str()

}

let h = handler {

{

get_str() = resume (“there”)

} in

let f (x) = � “Hey ” + get_str() + “, ” + x in

�let o1 = h(f(“Bob!”)) in��// continued on next panel

let g = handler {

{

get_str() = resume (“now”)

} in

�let o2 = g(f(“Bob!”)) in …��

�o2 = “Hey now, Bob!

o1 = “Hey there, Bob!”

21 of 31

I/O with Algebraic Effects

  • Imagine if your programming language�wrapped you entire program in a handler�

effect input

{

get_str()

}

let h = handler {

{

get_str() = resume (“there”)

} in

h(<your program>)

let f (x) = � “Hey ” + get_str() + “, ” + x �in

�f(“Bob!”)

Your Program

System start up program

22 of 31

I/O with Algebraic Effects

  • Imagine if your programming language�wrapped your entire program in a handler�

effect input

{

get_str()

}

let h = handler {

{

get_str() = resume (<console>)

} in

h(<your program>)

let f (x) = � “Hey ” + get_str() + “, ” + x �in

�f(“Bob!”)

Your Program

System start up program

23 of 31

I/O with Algebraic Effects

  • Imagine if your programming language�wrapped you entire program in a handler�

effect io

{

get_str(), put_str(s)

}

let h = handler {

{

get_str() = resume (<console>),

put_str(s) = � <s to console>;

resume (void)

} in

h(<your program>)

let f (x) = � “Hey ” + get_str() + “, ” + x �in

�put_str (f(“Bob!”))

Your Program

System start up program

24 of 31

Algebraic Effects

  • effect <name>
    • Declare a named effect, that you can call within the program.

  • handler
    • Specifies what do do when program requests an effect

  • resume <exp>
    • Resumes execution of the program with a value. Handles don’t necessarily need to resume and could instead abort program, or otherwise hijack control if desired (e.g., you can implementation an exception behavior).

25 of 31

Reasoning with Algebraic Effects

  • Algebraic effects also enable reasoning in that we can develop a type system the exposes the effects.

let f (x) : string -> io string =� “Hey ” + get_str() + “, ” + x �in

�put_str (f(“Bob!”))

effect io

{

get_str(), put_str(s)

}

System start up program

Your Program

Type of your program:

26 of 31

Reasoning with Algebraic Effects

  • Algebraic effects also enable reasoning in that we can develop a type system the exposes the effects.

effect io

{

get_str(), put_str(s)

}

System start up program

void -> io void

Type of your program:

Your Program

let f (x) : string -> io string =� “Hey ” + get_str() + “, ” + x �in

�put_str (f(“Bob!”))

27 of 31

Reasoning with Algebraic Effects

  • Algebraic effects also enable reasoning in that we can develop a type system the exposes the effects.

effect input {

get_str()

}

effect output {

put_str(s)

}

System start up program

void -> <input, output> void

Type of your program:

Your Program

let f (x) : string -> io string =� “Hey ” + get_str() + “, ” + x �in

�put_str (f(“Bob!”))

28 of 31

State with Algebraic Effects

  • I/O looks a lot like state. In fact, if we’d like, we can abstract mutable state using Algebraic Effects. See notes

effect input {

get_str()

}

effect output {

put_str(s)

}

effect read {

rd(l)

}

effect write {

wr (l, v)

}

f: string -> <rd> string

h: string -> <rd, wr> string

g : string -> <wr> string

What do the types of each of these functions tell us?

29 of 31

Conclusion: State

Mutability is unavoidable in modern systems

  • Coding: many programming patterns to organize program data that is a bit cumbersome to code functionally.
  • Data Structures: if we’d like to update a record or list, then what? Copying is a performance hit.
  • The outside world: modern programs interact with the world , such as filesystems, databases, distributed systems – other computers, or even physical actuation in robotics. We can’t copy the universe!��

30 of 31

Conclusion: State

  • In State we see two ends of the spectrum of language design
    • General Purpose Abstraction (Algebraic Effects) that can capture pretty much anything we want to call effects and thread it through the program.
      • Extensibility. Users can write their own effects and have them the system check them.
    • Special-Purpose Design (Program Data/State (References))
      • We went very deep in the design space. Custom abstractions (stack versus heap) that suit various sorts of intents (short-lived versus long data) with specific well-suit performance implementations (bump allocation versus garbage collection/or explicitly managed heaps).

31 of 31

Monads

  • If you seen the term Monad, I’m sorry .
  • But, are an alternative way to capture effects. Though they are subsumed by Algebraic Effects and seemingly are becoming little less popular. (IMO, Monads are kind of popular because the can be related to math and spoken about in very abstract and complicated terms. Don’t’ fall for the trap). https://blog.plover.com/prog/burritos.html. https://www.reddit.com/r/haskell/comments/6bxk1v/why_monads_always_get_compared_to_burritos/�https://twitter.com/monadburritos?lang=en
  • We can simulate a Monad with algebraic effect and vice versa: https://homepages.inf.ed.ac.uk/slindley/papers/effmondel-jfp.pdf