PLC > Objectives
Understand syntax vs semantics
Why is it more important to understand semantics than syntax?
Show that programming languages share many features
Most languages will have a subset of "all" programming features
PLC > Topics
Values and types
Memory
Environments
Variable bindings
Operators
Functions
Closures
Homogeneous types (lists)
Heterogenous types (tuples)
Enums and ADTs
Pattern matching
Destructuring
Currying
Generics
PLC > Values and types
A Value is a piece of data that can belong to one or more types and have zero or more operations performed on it
A Type is a set of possible values and a set of operations on those values
Type: (values: Set, operations: Set)
Examples:
String: ({ "a", "b", ... }, { concatenate, substring, ... })
Integer: ({ ..., -1, 0, 1, ... }, { +, -, *, / })
PLC > Memory
In computing, Memory is the space used to store values
0x01
0x00
0x02
0x03
0x04
0x05
0x06
0x07
char
short
int
long
Visualization of values of some C types
PLC > Memory > Addresses
An Address refers to the location of a value in memory
0x01
0x00
0x02
0x03
0x04
0x05
0x06
0x07
char
short
int
long
Address
In low-level programming languages, raw addresses are referred to as Pointers
'h'
'e'
'l'
'l'
'o'
'\0'
Address of 'e'
PLC > Memory > Structs
There are "compound" data types which combine values of several other types
char
int
In C, a struct defines a block of memory with named regions.
The size of the struct is the sum of the sizes of the regions*.
struct person {
int age;
char name[4];
}
struct person
age
name
PLC > Environments and Variable Bindings
An Environment is a mechanism used for mapping variable names to the value they represent.
// environment_example.c
int main(void) {
int foo = 42;
printf("foo: %d\n", foo);
return 0;
}
foo | 42 |
---|
Environment
Bindings
This environment's bindings
The extended environment
foo | 42 |
---|---|
... | ... |
A variable binding is a mapping from a name to a value
The empty environment
PLC > Environments and Variable Bindings
A Scope introduces a new environment that extends the previous environment
// environment_example.c
int main(void) {
int foo = 42;
printf("foo: %d\n", foo);
{
char *foo = "Hello";
printf("foo: %s\n", foo);
}
return 0;
}
foo | 42 |
---|
foo | "Hello" |
---|
Creating a new variable binding with the same name as an existing variable is called variable shadowing
PLC > Functions
A Function is a mapping of input values to an output value
A function's type is denoted using the types of its inputs and output
public String foo(char little, long big) { ... }
Formally, foo: char x long -> String
In programming, a function is executed by passing arguments to it as input
In math, a function is said to be applied to its input
PLC > Functions
In many programming languages, a function is itself a value
A Value is a piece of data that 1) can belong to one or more types and 2) have zero or more operations performed on it
Remember:
Let foo: int -> String
1) The value "foo" belongs to type int -> String
2) We can think of the type int -> String as having an operation called apply defined on it.
The "apply" operator can be seen as () in many languages
foo(42) can be read as "the function foo applied to the input 42"
PLC > Functions
Given foo: int -> String
Expression | Type |
---|---|
foo | int -> String |
42 | int |
foo(42) | String |
The type of a function applied to its input (e.g. foo(42)) is the type of the function's output
i.e. (int -> String) applied to int gives String
PLC > Functions
Function overloading means defining a new function type for an existing function name
Function | applied to | yields a |
---|---|---|
toString: int -> String | int | String |
toString: Person -> String | Person | String |
toString: String -> String | String | String |
Not that it would be good practice or intuitive, but you could also define toString: String -> int
Functions definitions must be unique. They can have the same name or the same type, but not both.
e.g. given the functions above, we could not define another toString: int -> String
PLC > Operators
An operator can be thought of as a function
e.g. + : int x int -> int
10 + 12 can be thought of as +(10, 12)
Operator overloading is the same thing as function overloading:
+ : int x int -> int,
+ : String x String -> String
PLC > Operators
Let's denote the type of an array as numbers: int[]
[ ] can be thought of as the "index" operator.
'numbers[5]' means "go the the address of the pointer called numbers, then increment the address by 5 times the size of int and get the value stored at that address"
An attempt at defining the index operator (pseudocode)
fn []<T>(name: *T [ index: usize ] ) -> T {
let offset = index * sizeof(T);
return *(name + offset);
}
PLC > Closures
A Closure is a function and the environment that its body is evaluated in
foo:
bar | console.log(`${bar}, ${baz}`); |
const foo = (bar) => console.log(`${bar}, ${baz}`);
formal arguments
body
environment
PLC > Closures
const key = "AAAAAAAAAAAAAHHHHHRHRGRGRGRRR...";
const pollFunction = () => {
return fetch("https://some.api.com/data", {
Authorization: `Basic ${key}`,
})
.then(result => result.json())
.then(result["answer"]);
};
setInterval(pollFunction, 5000);
key | "..." |
---|
pollFunction |
---|
()
...
PLC > Homogeneous types (lists)
A List is a variable-size set of values where each value in it is of the same type
foos: [String] = [ "foo", "fooo", "foooo" ]
PLC > Heterogeneous types (tuples)
A Tuple is an ordered, fixed-size set of values
// Define a tuple called 'point'
let point: (u32, u32) = (2, 4);
let person: (u32, String) = (20, "Bob");
let nested: (u32, (u16, (u8, String))) = (0, (1, (2, "Hi")));
The type of a tuple is given by the types of its values
point: (int x int)
The empty tuple is a tuple of length 0. The type of the empty tuple '()' is called Unit and has exactly one value, also called unit.
PLC > Heterogeneous types (tuples)
Mathematically, a tuple models a Cartesian product.
Let point: (int x int)
int
int
(1,1)
(2,1)
(3,1)
(4,1)
(1,2)
(2,2)
(3,2)
(4,2)
(-4,2)
(-3,2)
(-2,2)
(-1,2)
(-4,1)
(-3,1)
(-2,1)
(-1,1)
(1,-1)
(2,-1)
(3,-1)
(4,-1)
(-4,-1)
(1,-2)
(2,-2)
(3,-2)
(4,-2)
(-3,-1)
(-2,-1)
(-1,-1)
(-4,-2)
(-3,-2)
(-2,-2)
(-1,-2)
PLC > Heterogeneous types (tuples)
Mathematically, a tuple models a Cartesian product.
Let point: (int x String)
String
int
(1,"a")
(2,"a")
(3,"a")
(4,"a")
(1,"b")
(2,"b")
(3,"b")
(4,"b")
(1,"c")
(2,"c")
(3,"c")
(4,"c")
(-4,"a")
(-3,"a")
(-2,"a")
(-1,"a")
(-4,"b")
(-3,"b")
(-2,"b")
(-1,"b")
(-4,"c")
(-3,"c")
(-2,"c")
(-1,"c")
(1,"d")
(2,"d")
(3,"d")
(4,"d")
(-4,"d")
(-3,"d")
(-2,"d")
(-1,"d")
PLC > Enums and Algebraic Data Types (ADTs)
An Enum is an enumeration of all possible choices of a value
enum Color {
Red,
Green,
Blue,
}
In this example, the newly defined type is Color, and the newly defined values are Color::Red, Color::Green, and Color::Blue.
PLC > Enums and Algebraic Data Types (ADTs)
Enums in some languages can carry data. These are sometimes called sum types or tagged unions.
enum Option<T> {
Some(T),
None,
}
In the Option type, a value may either carry no data (None), or carry Some data of type T.
In order to distinguish between which variant a value of an enum type is, we need to use pattern matching.
PLC > Pattern Matching
Pattern Matching is a means of deconstructing a compound type into its component types.
enum Color {
Red,
Green,
Blue,
}
let mystery_color: Color = Color::Blue;
match mystery_color {
Color::Red => println!("Got the color red"),
Color::Green => println!("Got the color green"),
Color::Blue => println!("Got the color blue"),
}
When matching against enums with no data, the match statement works similarly to a switch statement.
PLC > Pattern Matching
Pattern matching generally requires a match statement to be exhaustive, or account for every variant case.
enum Color {
Red,
Green,
Blue,
}
let mystery_color: Color = Color::Blue;
match mystery_color {
Color::Red => println!("Got the color red"),
Color::Green => println!("Got the color green"),
// Color::Blue => println!("Got the color blue"),
}
The above example fails to compile because case Color::Blue is not handled.
PLC > Pattern Matching
When pattern matching enums with data, the pattern can create variable bindings that map to the contained values.
enum Option<T> {
Some(T),
None,
}
fn square_root(number: f32) -> Option<f32> { ... }
let number = -4.0;
let maybe_root = square_root(number);
match maybe_root {
None => println!("Cannot take the square root of a negative"),
Some(root) => println!("Got a square root of {}", root),
}
If the value of maybe_root is Some, the value inside it is bound to the name root for the duration of the match arm.
PLC > Algebraic Data Types
Proper enums (aka Tagged Unions or Sum Types) are known as Algebraic Data Types because the set of possible values of a given enum type is fixed.
When we know all of the possible values of a type, we can reason more strongly about the possible behavior of programs.
PLC > Algebraic Data Types
Consider the following ADT:
enum Color { Red, Green, Blue }
enum Row { A, B, C }
enum Column { 1, 2, 3 }
ColorPoint: (Row, Column, Color)
We can visualize the entire domain of values of the ColorPoint type.
PLC > Algebraic Data Types
enum Color { Red, Green, Blue }
enum Row { A, B, C }
enum Column { 1, 2, 3 }
ColorPoint: (Row, Column, Color)
1
2
3
A
B
C