Semantics And Syntax Of High Level Languages
1 Data Abstraction
1.1 Symbol names
User can define symbol names based on the semantic meaning of the variables.
total = 0
...
total += counter1.2 Type annotation
User can annotate values by type information so to make verification (type checking) possible.
int total = 0;
...
total += counter;1.3 Rich data structures
The JVM runtime memory layout (stack, local vars, and heap space) is for performance reasons. Programming languages offer rich data structures to express memory layouts that are natural to the needs of the application.
1.4 Arrays:
names = [
    'Albert Einstein',
    'Alan Turing',
    'David Hilbert',
]1.5 Dictionaries:
course = {
    'title': "Compilers",
    'schedule': {
        "Wednesday": {
            "start": "8am",
            "end": "9:30am"
        }
    }
}1.6 Strongly typed data structure
We can give semantically significant names to types.
type StudentID = String
type Name = String
type Street = String
type City = String
type Province = StringWe can define more complex data types.
data Address = Address {
  street :: Street,
  city :: City,
  province :: Province
}
data Student = Student {
  studentID :: StudentID,
  name :: Name,
  address :: Address
}Ultimately we can model arrays more precisely.
type Class = [Student]This allows us to create instances safely:
sampleClass :: Class
sampleClass = [
    Student {
        studentID = "001",
        name = "Alice Smith",
        address = Address {
            street = "123 Maple Street",
            city = "Springfield",
            province = "StateA"
        }
    },
    Student {
        studentID = "002",
        name = "Bob Johnson",
        address = Address {
            street = "456 Oak Avenue",
            city = "Riverdale",
            province = "StateB"
        }
    }
]1.6.1 Advanced typing
How do we ensure that studentID is a string that only contains numerical digits?
Haskell offeres the advanced feature known as dependent type:
import Data.Char (isDigit)
mkStudent :: String -> Name -> Address -> Maybe Student
mkStudent sid name addr
  | all isDigit sid = Just $ Student sid name addr
  | otherwise       = NothingIt allows code that is even more safe:
sampleClass :: Maybe Class
sampleClass = sequence [
    mkStudent "001" "Alice Smith" (
        Address "123 Maple Street" "Springfield" "StateA"
    ),
    mkStudent "002" "Bob Johnson" (
        Address "456 Oak Avenue" "Riverdale" "StateB"
    ),
]The code sampleClass is safer in the sense that it forces the programming to write code to deal with situations where sampleClass is not a valid class, and instead is Nothing.
2 Runtime Abstraction
2.1 Binding and scopes
Variables are assigned to values.
var x = 10
fmt.Println(x)2.1.1 Nested scoping rule
However, there is the notion of scopes, and scopes can be nested.
var x int = 10
{
    var y int = 20
}
fmt.Println(x) // ✅
fmt.Println(y) // ❌2.1.2 More structured scope functions
// Kotlin
var student = Student(
    "002",
    "Bob Johnson",
    Address("456 Oak Avenue", "Riverdale", ...)
).also(
    val student = this.name
    println("${student} address is ${this.address}")
)There are two scopes:
- the main scope has a binding between variable studentto the object.
- the subscope has a binding between value studentto the student name, andthisto the object.
2.2 Threads
2.2.1 Multicore architecture

2.2.2 Structured threaded execution
import kotlin.concurrent.thread
thread(start=true) {
    ... // code to run in a new thread.
}
thread(start=true) {
    ... // code to run in another new thread.
}2.2.3 Interleaving threads via communication

2.2.4 Inter-thread communication
var count = 0
var total = 0.0
var lock = Any()fun increment(amount:Double) {
    synchronized(lock) {
        count ++
        total += amount
    }
}
fun decrement(amount:Double) {
    synchronized(lock) {
        count --
        total -= amount
    }
}The two functions increment and decrement will never overlap.
val t1 = Thread {
    for(i in 1..5) {
        increment(Random.nextDouble())
    }
}val t2 = Thread {
    for(i in 1..5) {
        decrement(Random.nextDouble())
    }
}We can management threads with manual control.
t1.start()
t2.start()
t1.join()
t2.join()
// counter should be zero2.3 Coroutines and channels
- Coroutines are blocks of code that are allowed to be executed interleaved. - a relaxed version of threads
- each thread can execute multiple coroutines
- coroutines can be interleaved using co-operative multiplexing
- highly scalable (> 1,000,000 coroutines)
 
2.3.1 Golang coroutines
go func() {
    ... // runs concurrently with multiplexing
}()The general syntax is:
go f(...)where the function f(...) is executed in a coroutine.
Here we are creating an anonymous function, and invoking it immediately.
func() {
  // code to be executed
}()Go has a highly efficient dispatcher that can support millions of coroutines on modern CPUs, making it so ideal for server development.
2.3.2 Golang channels
Channels are communicating objects that can be shared among coroutines.
var ch chan int = make(chan int)ch <- 42var number = <-ch2.3.3 An example of producer / consumer pattern

func producer(ch chan<- int, group *sync.WaitGroup) {
    for i := 1; i <= N; i++ {
        var value int = makeValue()
        ch <- value
    }
    group.Done()
}The producer function makes \(N\) integer values, and sends them to the channel ch. At the end, it also indicates that it is done.
func consumer(ch <-chan int) {
    for value := range ch {
        saveValue(value)
    }
}The consumer function reads all the data from the channel until it is closed (remotely).
Let’s put it all together:
var NUM_PRODUCERS = 1000000var ch = make(chan int)This is a shared channel.
var group sync.WaitGroup
This is a shared count-down synchronization data structure.
- group.Wait()blocks until the lock value reaches zero.
- group.Done()decrements the lock value by one.
go consumer(ch)
We can safely start consuming even before the producers have started.
- It will momentarily block at reading from the channel.
- It will also terminate after the channel is closed.
group.Add(NUM_PRODUCERS)
for i := 0; i < NUM_PRODUCERS; i++ {
    go producer(ch, &group)
}- Initially, we set the count-down value to NUM_PRODUCERS.
- Start NUM_PRODUCERScoroutines.
group.Wait()
close(ch)
- Wait for all producers to finish
- When all the producers are done, we can safely close the channel.
- When the channel is closed, then the consumer will also terminate.
3 Summary
3.1 Design of runtime environment
- Scopes, modules, and functions
- Data types
- Concurrency: thread / coroutine / channels
3.2 Syntax
- Nested code blocks, anonymous function
- Type system and type annotations
- Structured concurrency and synchronization
3.3 What’s the point?
- Innovative runtime environment
- requires innovative syntax to boost productivity and improves reliability.