Computer Science / Software Engineering Notes Network

Programming Language Concepts

Matthew Barnes

Syntax to execution        2

Introduction        2

Syntax and BNF        4

Names, scoping and binding        7

Lexing and parsing        8

In Haskell        9

Testing        10

Introduction to types        10

Type derivation        12

Type checking and type inference        14

Reasoning about programs        19

Intro to semantics        19

Denotational semantics        19

Operational semantics        20

Type safety        23

Preservation        24

Progress        25

Structured types        26

Structured types        26

Subtyping        31

Objects        39

Lambda calculus and Curry Howard        46

Fixed-point combinator        49

Curry-Howard correspondence        51

Concurrency in programming languages        54

Race conditions and mutual exclusion        54

Atomicity, locks, monitors        58

Barriers and latches, performance and scalability, CAS and fine-grained concurrency        61

Message passing, software transactional memory        65

Rust and concurrency        70

Semantics of concurrency        73

Labelled Transitions Systems        73

Simulations        76

Bisimulations        82

TL;DR        84

Syntax to execution        84

Testing        86

Reasoning about programs        87

Structured types        88

Concurrency in programming languages        89

Semantics of concurrency        90

Syntax to execution

Introduction

Syntax and BNF

<expr> ::= <expr> + <expr> | <expr> * <expr> | <lit>
<lit>  ::= 1 | 2 | 3 | 4

Derivation #1

Derivation #2

<expr>  ::= <mexpr> + <expr> | <mexpr>

<mexpr> ::= <bexpr> * <mexpr> | <bexpr>

<bexpr> ::= (<expr>) | <lit>

<lit>   ::= 1 | 2 | 3 | 4 | ...

<expr>  ::= <expr> + <mexpr> | <mexpr>

<mexpr> ::= <bexpr> * <mexpr> | <bexpr>

<bexpr> ::= (<expr>) | <lit>

<lit>   ::= 1 | 2 | 3 | 4 | …

Usage

Notation

definition

=

concatenation

,

termination

;

alternation

|

optional

[ ... ]

repetition

{ ... }

grouping

( ... )

terminal string

“ ... “

terminal string

‘ ... ‘

comment

(* ... *)

special sequence

? ... ?

exception

-

<if_stmt> ::= if <expr> then <stmt> [else <stmt>]

The [else <stmt>] is optional because it’s enclosed in square brackets

<switch_stmt> ::= switch (<var>) { { case <lit> : <stmt>; } }

The {case <lit> : <stmt>;} can be repeated because it’s enclosed in curly braces

Names, scoping and binding

Lexing and parsing

<expr>   -> <term> {(+ | -) <term>}

<term>   -> <factor> {(* | /) <factor>}

<factor> -> id | int_constant | (<expr>)

In Haskell

Testing

Introduction to types

Example

Result

Explanation

1 + true

2

The boolean value true is coerced to 1.

true - true

0

true == 1

true

[] + []

empty string

In case of an object (array is an object as well), it is first converted to a primitive using the toString method. This yields an empty string for an empty array, and the concatenation of two empty strings is an empty string.

true === 1

false

The triple equals operator prevents type coercion. This is why most of the time you want to prefer it over “==”, which applies coercion.

91 - “1”

90

The string “1” is coerced to the number 1.

9 + “1”

91

In this case, the “+” operator is considered as string concatenation. 9 is coerced to “9” and thus the result of “9” + “1” is 91.

Weak

Strong

Dynamic

PHP, Perl, Javascript, Lua

Python, Lisp, Scheme

Static

C, C++

Ada, OCaml, Haskell, Java, C#

Type derivation

Type checking and type inference

Step

Explanation

Program

Types

0

We haven’t done anything yet.

let foo = λ(x) if (x < 3) then 0 else (x + 1)

in let cast = λ(y) if (y) then 1 else 0

in cast (foo (42))

N/A

1

We will now simplify the first let.

We’re going to create type variables for the variable ‘foo’ and the expression that ‘foo’ is equal to, and the rest of our program.

let foo = λ(x) if (x < 3) then 0 else (x + 1)

in let cast = λ(y) if (y) then 1 else 0

in cast (foo (42))

a = ?

b = ?

2

Now we’re going to dig deeper into that λ(x)

We can give type variables to the parameter x and the lambda body.

Also, now that we have c and d, we can build up the type variable a from that, because we know it’s a function (it’s got lambda in it), so a is c -> d.

let foo = λ(x) if (x < 3) then 0 else (x + 1)

in let cast = λ(y) if (y) then 1 else 0

in cast (foo (42))

a = c ⟶ d

b = ?

c = ?

d = ?

3

Let’s look more at that if statement.

Obviously, x < 3 is a boolean, so no problems here. Plus, this condition can only work if x is an integer, so it’s likely that the type variable c is an integer.

0 is most definitely an integer, and x + 1 looks like it could be an integer too, so it would be fair to say that the type variable ‘d’ is an integer.

let foo = λ(x) if (x < 3) then 0 else (x + 1)

in let cast = λ(y) if (y) then 1 else 0

in cast (foo (42))

a = c ⟶ d

b = ?

c = Integer

d = Integer

4

Now let’s split up that blue bit (the bit we haven’t evaluated yet).

Let’s give everything type variables first, like ‘cast’ and the expression that cast equals, and cast(foo(42)).

This also means we can say b = f, because the type of a let is the type of the expression it’s passing variables to.

let foo = λ(x) if (x < 3) then 0 else (x + 1)

in let cast = λ(y) if (y) then 1 else 0

in cast (foo (42))

a = c ⟶ d

b = f

c = Integer

d = Integer

e = ?

f = ?

5

Let’s unfold that λ(y) and give it the type variable:

e = g -> h

where

(y) is type g

and

if (y) then 1 else 0 is type h

let foo = λ(x) if (x < 3) then 0 else (x + 1)

in let cast = λ(y) if (y) then 1 else 0

in cast (foo (42))

a = c ⟶ d

b = f

c = Integer

d = Integer

e = g ⟶ h

f = ?

g = ?

h = ?

6

Let’s unfold the if statement in that y lambda.

It’s clear that y is a boolean, because it’s the only thing in the condition, so g is a boolean type.

1 and 0 are integers, so it makes sense that h would be an integer.

let foo = λ(x) if (x < 3) then 0 else (x + 1)

in let cast = λ(y) if (y) then 1 else 0

in cast (foo (42))

a = c ⟶ d

b = f

c = Integer

d = Integer

e = g ⟶ h

f = ?

g = Boolean

h = Integer

7

Let’s unfold the applications now.

The application is cast(foo(42)), and we already know the type of cast; it’s h, so f = h.

By unwinding foo(42), we can tell that c is an integer because an integer is being passed in as an argument. We’ve already got the type of c, and it all matches up fine.

But here’s where the problem lies: since the return type of foo is being passed into cast, that must mean g = d. However, g is a boolean and d is an integer. Objection! There is a glaring contradiction in this source code!

let foo = λ(x) if (x < 3) then 0 else (x + 1)

in let cast = λ(y) if (y) then 1 else 0

in cast (foo (42))

a = c ⟶ d

b = f

c = Integer

d = Integer

e = g ⟶ h

f = h

g = Boolean

h = Integer

g = d

Boolean = Integer?

Reasoning about programs

Intro to semantics

Denotational semantics

Operational semantics

Small step relation

Explanation

Here, the program n < m moves to just ‘true’. The only way that this can happen is if n is less than m, so the predicate n < m is at the top.

Here, the program moves from n < E to n < E’. If the program is simply replacing E with E’, then that means E must be equivalent to E’ in some way. Therefore, the step E -> E’ is at the top.

Here, the program is moving from an if statement to the program that should run if the condition is true. We can see that the condition is the literal ‘true’, so this program makes perfect sense. There’s no need to prove anything further because there’s no ambiguity or room for doubt; this is exactly what should happen.

Type safety

Preservation

  1. E1 is some expression and can be reduced from E1 to E1’
  1. E1 is some literal and the expression is either E2 or E3.

Progress

  1. E1 is not a value
  1. E1 is a value

Structured types

Structured types

Record

C struct

Java object / class

Student : {

  name : String,

  ID : Integer

}

struct Student {

  char* name;

  int ID;

};

class Student {

  String name;

  int ID;

};

Type rule in weird maths language

Type rule in English

Each value in the record Ei has a respective type Ti

Pretty much the same, except we use selections

Type rule in weird maths language

Type rule in English

When we construct a value from this variant, the value must have the type that the label’s injection is assigned to.

When we do a pattern match, or a case statement, the value inside the case statement must be a variant. Additionally, for each ‘case’ within the case statement, the pattern match’s type must match up with the variant’s type (and the return type of the whole case statement must match up too, obviously).

Subtyping

class Address {

  String name;

  String address;

}

class Email {

  String name;

  String address;

}

A Dog can be treated as a subtype of a Dog

If a JackRussell is a Dog, and a Dog is an Animal, then a JackRussell is an Animal

Records

JavaScript

Dog : {

  colour : String,

  age : Int,

  breed : String

}

<:

Animal : {

  colour : String,

  age : Int

}

// This function expects an animal

function getAge(animal) {

  return animal.age;

}

let dog = {

  colour: “brown”,

  age: 10,

  breed: “Golden Retriever”

};

getAge(dog); // This will work because of width subtyping

Records

JavaScript

Dog : {

  colour : String,

  age : Int,

  breed : String

}

<:

Dog : {

  breed : String,

  colour : String,

  age : Int

}

// This function expects an animal

function getAge(animal) {

  return animal.age;

}

let animal1 = {

  colour: “brown”,

  age: 10

};

let animal2 = {

  age: 4,

  colour: “red”

};

// These will both work because of this subtyping rule

getAge(animal1);

getAge(animal2);

How do we know that this is type-safe? We use the type rules above!

We’ll use the depth subtyping rule, because the left S is a subtype of the right S and the left R is a subtype of the right R.

After that, we now have two terms to simplify.

We’ll now use the width subtyping rule on the right term, because the left record has one extra property compared to the empty right record.

This ends that branch, so now we can do the left.

Here, we can use the transitivity type rule to split up the terms.

You may be thinking “why not just use the width subtyping rule?”

The width subtyping rule only supports one extra property, so we need to use the transitivity rule to split up the terms until the records differ by one property. Don’t worry though, it’s not a solid rule; it’s just a formality.

Finally, we use the width subtyping rules to finish the branches, since the records now differ by one property.

// note that Number is a super class of Integer and Double

public static Number foo(Number x) {

        return x.intValue();

}

Number integer = foo(2.5);

Objects

Declaration

Explanation

The main program, called , contains a set of all the classes within the program, .

A class, , needs to have a name , a parent class , a constructor , as well as a set of fields  and a set of methods .

A constructor, , needs to have the name of the class it constructs, , along with the inherited properties, , and its own properties, , as parameters to the constructor.

It must also start with the method, along with the inherited properties as the arguments, . Afterwards, the members must be assigned their values as supplied by the parameters, .

A field declaration, , must have a type, , and a name, .

A method  must have a return type , a name , parameters  and a return expression . In Java Jr, that’s all a method can do. Additionally, a method must return something.

An expression  can be:

  • A literal
  • A variable
  • A method call
  • A field reference
  • A field assignment
  • A class instantiation
  • A ternary expression (like an if statement, but it returns something)

Rule

Explanation

The  function takes in a class and returns the name of the class.

We can only reference a class from the program  (remember,  contains a set of classes) if the field name we use is the same name as the class (pretty obvious).

The  class has no methods (in Java it does, but in Java Jr it doesn’t).

When a class  inherits from class , the methods within  are the methods defined in  as well as the methods defined in .

The  class has no fields.

When a class  inherits from class , the fields within  are the fields defined in  as well as the fields defined in .

The  class has no method signatures (think of them like how C declares functions).

When you try to reference the method signatures of a class , you’ll get all the signatures of the methods of class  as well as the signatures of the parent class .

If you try to reference the signatures of a method, you’ll just get the signature of that method.

Typing relation

Explanation

The Object class is a type.

The int primitive is a type.

The bool primitive is a type.

 is a type, if it’s a class defined within the program .

Subtyping relation

Explanation

The reflexive rule for types. Any type can be seen as a subtype of itself.

Every type is a subtype of the  class (in Java Jr, this includes primitives, but in Java primitives are not included).

The transitive rule for types. If a JackRussell is a Dog, and a Dog is an Animal, then JackRussell is an Animal.

If a child class inherits from a parent, then the child class is a subtype of the parent (yes, this includes primitives as well, but not for normal Java).

This bit states that the parent class must be some valid type. Simple enough.

This bit states that the new fields that  introduces must also have valid types.

This bit states that all the methods in  must be valid.

This bit states that all the signatures in the parent class must also exist in the child class. The child class doesn’t have to override them, but the method signatures should still exist.

This bit states that all of the fields in the parent class must be covered by the constructor.

This bit states that the return type must be a well-typed type.

This bit states that the parameters types must be well-typed.

This bit states that the return expression is of the correct type. Since the parameters will be used in , it has been added to the type environment, as well as the  keyword.

Typing relation

Explanation

This is just an integer literal. Nothing special here.

Again, it’s just a boolean literal.

We can reference a variable only if it exists in the type environment with the correct type.

When we instantiate an instance of a class, we must ensure that:

  1. The class exists and is well-typed
  2. All of the fields of the class are provided in the constructor call, including inherited fields (because in Java Jr, the constructor initialises every field in a class)

When we use a ternary operator, the two expressions being compared must be of the same type.

Additionally, the two expressions that may be returned must be of the same type as the entire expression.

When we reference a field from an object, that field must have the same type as the expression, and the field has to exist in that class.

When we assign a value to a field, it’s pretty much the same as the above, but the new value must be the same type as the field we’re putting it into.

When we run a method call, the method must exist as one of the class’ method signatures.

Additionally, the expressions we’re passing in as arguments must be well-typed and be the same types as the parameters the method expects in the method signature.

Lastly, the type of the method must be the same as the type of the method call.

This rule lets us use subtyping in Java Jr. Instead of integrating it inside other rules, we can use the subtyping rule separately, then any other rule of our choice.

Lambda calculus and Curry Howard

Big step rule

Explanation

If you have a function on its own, it can’t be simplified any further.

If you have an application, the left must simplify to a function, the right must simplify to some value, and when you substitute the argument into the function body, the resulting expression must match up with the expected value.

Small step rule

Explanation

When you do function application, and you substitute the argument for the parameter, everything is fine. That’s normal behaviour.

If you have a function application, but the LHS can be reduced first, then do that.

If you have a function application, the LHS is a value and the RHS can be simplified, then do that.

  1. Your name is Dr Julian Rathke
  2. You really have nothing to do with your life

Fixed-point combinator

fix f = f (fix f)

  1. Your first parameter must be a function
  2. That first parameter function must be used in your recursive calls instead of the function name
  3. Prepend your calls with the fix function
  1. fib f 0 = 0

fib f 1 = 1

fib f n = fib (n-1) + fib (n-2)

  1. fib f 0 = 0

fib f 1 = 1

fib f n = f (n-1) + f (n-2)

  1. fibb = fix fib

Type rule

Explanation

If we’re using the fix function, whatever is being passed in must also be a function, whose return type and parameter type must be the same as the type of the fix call.

Small step rule

Explanation

When we call the fix function, replace all instances of x within E with the fix function again (to create recursion).

If we call the fix function, and the argument can be simplified, then simplify it.

Curry-Howard correspondence

Proof rule

Typing rule

Where  is a function like fst

Where  is a function like snd

Concurrency in programming languages

Race conditions and mutual exclusion

double accountBalance;

void withdraw(double out)

{

  if (accountBalance > outAmount)

    accountBalance = accountBalance - out;

}

void credit(double in)

{

  accountBalance = accountBalance + in;

}

Thread #1

Thread #2

Check if we have enough to withdraw.

We have enough to withdraw!

Fetches the value of accountBalance, which is 30.

Calculates the new value for accountBalance by doing 30 - 10, which is 20.

Fetches the value of accountBalance, which is 30.

Calculates the new value for accountBalance by doing 30 + 20, which is 50.

Updates the accountBalance variable with 50.

Updates the accountBalance variable with 20.

Thread #1

Thread #2

while (1)

{

  while (lock);

  lock = 1;

  critical_region();

  lock = 0;

  noncritical_region();

}

while (1)

{

  while (lock);

  lock = 1;

  critical_region();

  lock = 0;

  noncritical_region();

}

Thread #1

Thread #2

while (1)

{

  while (turn != 0);

  critical_region();

  turn = 1;

  noncritical_region();

}

while (1)

{

  while (turn != 1);

  critical_region();

  turn = 0;

  noncritical_region();

}

bool flag[2] = {false, false};

int turn;

flag[0] = true;

turn = 1;

while (flag[1] == true && turn == 1)

{

    // busy wait

}

critical_region();

flag[0] = false;

flag[1] = true;

turn = 0;

while (flag[0] == true && turn == 0)

{

   // busy wait

}

critical_region();

flag[1] = false;

  1. The other thread doesn’t want to access its critical region
  2. The thread is letting the other thread access their critical region

Atomicity, locks, monitors

Barriers and latches, performance and scalability, CAS and fine-grained concurrency

Message passing, software transactional memory

  1. Open up two terminals
  2. Run erl -sname hi in one
  3. Run erl -sname hi2 in the other
  4. You will now see something like (hi@pcname)1> and (hi2@pcname)1>
  5. To ping a node, run net_adm:ping('hi@pcname'). in the hi2 shell (obviously, replace pcname with whatever it says on the hi node).
  6. You’ll see it prints just ‘pong’, but that ‘pong’ came from the hi node!
  7. To see a list of visible nodes, run nodes().
  8. If you run that in hi2, you’ll see hi, and if you run it in hi, you’ll see hi2.

The thick arrows are paths in the tree, and the dotted lines are pointers

Table

Explanation

Time

Data

0s

Foo: 56 by Alice

Bar: 42 by Alice

Alice and Bob have shared memory they want to use.

Alice and Bob can commit changes and read snapshots at any time.

Alice wrote the Foo and Bar objects before they started.

Time

Data

0s

Foo: 56 by Alice

Bar: 42 by Alice

10s

Bar: 20 by Bob

10 seconds pass, and Bob commits a change to the Bar object. It’s now 20.

Alice reads the value of Foo and Bar at the 5 second mark, and gets the values 56 and 42, respectively.

But then Alice reads the value of Foo and Bar at the 15 second mark, and instead gets 56 and 20, respectively.

The new value that Bob writes supersedes the previous value, because it was written after.

Time

Data

0s

Foo: 56 by Alice

Bar: 42 by Alice

10s

Bar: 20 by Bob

20s

Bar: (deleted) by TS

FooBar: “pawel” by Bob

Now, at the 20 second mark, someone else comes in, known only by the initials “TS”, and deletes Bar!

At the same time, Bob commits a new object called FooBar.

At the 18 second mark, Alice still reads Foo = 56 and Bar = 20. Alice is unaware of FooBar until that 20 second mark.

However, at the 26 second mark, Alice reads again and realises that Bar was deleted and FooBar was created.

Rust and concurrency

Code

Explanation

fn main() {
 
let s = String::from("hello");
  takes_ownership(s);

 
// ERROR: println!("{}", s);

 
let x = 5;
  makes_copy(x);

 
println!("{}", x);
}
fn takes_ownership(some_string: String) {
 
println!("{}", some_string);
}

fn makes_copy(some_integer: i32) {
 
println!("{}", some_integer);
}

Let’s start at the main function.

On the first line, we create a string that contains ‘hello’.

Then, we pass that string into the takes_ownership function. That function now has ownership of that string, so we can’t use it in the main function anymore. If we were to uncomment that println line that accesses the string s, we would receive an error.

Next, we create an integer that contains ‘5’.

We then pass that into the function makes_copy. Because the type of x, i32 (integer 32 bit), implements the Copy trait, a copy of the value is made and we can still use the variable x inside main.

At the end of the takes_ownership function, drop is called implicitly and the memory that accommodates the string is freed.

You can run this code here: https://ideone.com/tgO6S2

Code

Explanation

fn main() {
 
let s1 = String::from("hello");
 
let len = calculate_length(&s1);
 
println!("The length of '{}' is {}.",
    s1, len);
}

fn calculate_length(s: &String) -> usize {
  s.len()
}

fn change(some_string: &String) {
  some_string.push_str(
", world");
}

Like before, we create a string.

When we define len, we use the calculate_length function. Notice that we pass in &s1, not s1. This is a ‘reference’ to s1, and calculate_length doesn’t claim ownership, but borrows it instead.

References are immutable, so calculate_length can’t mutate the string s. That’s why the function change wouldn’t work, if you tried it.

You can run this code here: https://ideone.com/VhT9Gd

Code

Explanation

fn main() {
 
let mut s = String::from("hello");
 
let r1 = &mut s;
 
let r2 = &mut s;
 
println!("{}, {}", r1, r2);
}

Here, we make a string, and try to create two mutable references of that string. This will throw an error, since there can only be one mutable reference of the string.

You can run this code here: https://ideone.com/tQjndr

Code

Explanation

use std::thread;
use std::time::Duration;
fn main() {
 
let handle = thread::spawn(|| {
     
for i in 1..10 {
         
println!("hi number {} from the spawned thread!", i);
          thread::sleep(Duration::from_millis(
1));
      }
  });
 
for i in 1..5 {
     
println!("hi number {} from the main thread!", i);
      thread::sleep(Duration::from_millis(
1));
  }
  handle.join().unwrap();
}

Here we use the thread::spawn function to spawn in a thread.

Both the main thread and the new thread loops through a set of numbers and prints from 1 to n.

In the output, you can see the counting alternate between both threads.

The last line, handle.join().unwrap(), just makes the main thread wait for the new thread.

You can run this code here: https://ideone.com/I0srJN

Code

Explanation

use std::thread;
fn main() {
 
let v = vec![1, 2, 3];
 
let handle = thread::spawn(|| {
     
println!("Here's a vector: {:?}", v);
  });
  handle.join().unwrap();
 
drop(v);
}

Here, we create a thread, just like last time.

We also create a vector, which is a list (similar to Vector in Java).

The thread tries to use the vector v, but this code will throw an error because the main thread never gave ownership of v to the new thread.

Code

Explanation

use std::thread;
fn main() {
   
let v = vec![1, 2, 3];
   
let handle = thread::spawn(move || {
       
println!("Here's a vector: {:?}", v);
   });
   handle.join().unwrap();
   
//drop(v);
}

This is the same code as before, except now we use the move keyword when we spawn the new thread.

Since we’re moving ownership, we can’t drop v at the end of the main thread, because the main thread doesn’t own v anymore. Therefore, if we were to uncomment that line, we would get an error.

Semantics of concurrency

Labelled Transitions Systems

x = 0;

print(“x is “ + x);

x = 1;

x = 2;

Simulations

LTS’

Explanation

This is our initial state. Both LTS’ are in the x0 and y0 states.

y0 can perform the £ transition and go to either y1 or y2. x0 can simulate this by also performing the £ transition and going to x1.

For now, we’ll go to y1, and we’ll see what happens when we go to y2 later.

Now, the only transition y1 can make is t, taking it to y3. x1 can also perform t, taking it to x2.

No more moves can be made, so this branch is fully simulated.

But what happens if, from before, we go to y2 instead of y1? Will it be any different?

No! Because y2 can only perform the c transition, taking it to y4. x1 can also perform the c transition, taking it to x3. Now all branches have been simulated, and we can conclude that x0 simulates y0.

  1. We start at position
  2. The demon picks a move
  3. We pick a move
  4. The game goes back to step 2, with position

LTS’

Explanation

This is our initial state. Both LTS’ are in the x0 and y0 states. The left is the demon and the right is the player.

First of all, the demon picks the transition “£” and moves to x1.

Since the demon picked £, we have a choice of either the £ that goes to y1 or the £ that goes to y2.

For now, we’ll go to y1.

At this state, the demon can pick the c move and trap us. There is no c move at y1! Here, the demon has won. This is usually enough to prove that y0 does not simulate x0.

The same goes for if we were to pick y2. The demon would end up picking the t transition, and we wouldn’t be able to move, since y2 only has the c transition. The demon wins in every case, so y0 does not simulate x0!

Bisimulations

  1. The demon picks where to play, either at x or at y
  2. If demon chose x, he picks a move

in this case we must respond with

  1. If demon chose y, he picks a move

in this case we must respond with

  1. The game goes back to step 1, changing position to

LTS’

Explanation

This is our initial state. Both LTS’ are in the x0 and z0 states. The demon wants to move z first, meaning us, the player, will have to move x next.

The demon moves from z0 to z2 using the transition £.

We also need to use the transition £, so we move from x0 to x1, the only move we can make.

It is now the demon’s turn and they’ve decided to switch systems! Now the demon can move x, forcing us to move z on the next go!

The demon has decided to move from x1 to x3 using the transition c. At our position, there are no transitions we can take named c, so now the demon has a winning strategy, showing that these two systems are not bisimilar.

TL;DR

Syntax to execution

Testing

Reasoning about programs

Structured types

Concurrency in programming languages

Semantics of concurrency

  1. We start at position
  2. The demon picks a move
  3. We pick a move
  4. The game goes back to step 2, with position

  1. The demon picks where to play, either at x or at y
  2. If demon chose x, he picks a move in this case we must respond with
  3. If demon chose y, he picks a move in this case we must respond with
  4. The game goes back to step 1, changing position to

Julian is happy you finished the PLC notes

"Hey there, Julian here! I see you've done plenty of revision, right...? *nods expectantly* What? Wait? What do you mean you've left it to the last minute? Oh oh! You'll never write type-safe programs if you don't revise! No worries, the notes always have your back. I've got to go now; a Java developer is implementing invariant arrays, making Java not type-safe! I'll see you around, alright...? *nodding again, looking around expectantly*"