When designing a new language, ease of programming is almost always the main driving force
One of the core issues with language design is names, since they’re something that machines aren’t very good at handling: abstractions
As we get higher in levels and farther from the hardware, our languages become much more abstract to manage complexity of modern programs, but this is also a double edged sword since it makes it harder for machines to handle
We can divide these semantics into three parts:
Binding can be static (bound before run time) or dynamic (bound at run time), which gives you either better efficiency or better flexibility, respectively
The lifetime of this binding is the time between it’s creating and distruction, while its scope is the textual region where it’s active
There’s many ways to store this, either statically (absolute address), stack (for subroutine calls and returns), and the heap (dynamically allocated memory)
Static allocation is used for stuff that is very explicit, such as global variables, instructions, explicit constants or static variables, such as in C++
With stack-based allocation, we take all our parameter, local variables and temporaries and throw them into a frame, which represents a subroutine call (this should be review from 2208)
Heap allocation throws data into our RAM, allowing for us to expand our variables at will
Allocating these variables with a first-fit algorithm is $O(n)$, but we can use pool allocation instead for $O(1)$ time
Pool allocation uses a free list of blocks of different sizes, either with a buddy system with blocks of size $2^k$ or a Fibonacci heap with Fibonacci-sized numbers
The issue with the heap is deallocation since it’s throwin into RAM, which is either done through explicit deallocation (C, C++, other low level languages) or implicit deallication/garbade collection (Python, Java)
Explicit allication is usually simpler to implement and faster, but this comes with some problems for the programmer: deallocate too soon and you get a dangling reference, but if you don’t deallocate at the end of lifetime, you get a memory leak
The scope, as we’ve mentioned, is the textual region of the program for which the binding is active
A subroutine will usually create a new scope where we create local bindings and destroy them once the subroutine is exited, restoring the bindings for global variables
Almost all languages employ static scoping, where scope is determined at compile time
For subroutine, we might have nested subroutines, for which we will use the closest nested rule such as in Python and Scheme, but these don’t exist in languages such as C, C++ or Java
Sometimes we might have a scope resolution operator for variables that are non-local
Something that’s (luckily) not used anymore is dynamic scoping, where binding depends on the flow at run time, which is easier to implement (just a stack), but its harder to understand
How this works is that, when we enter a new scope, we overwrite bindings by putting them on the stack
This comes with its own problems, where the wrong variable is used in some functions
The reference environment is the set of active binding, handled by lexical nesting in static scope and order of declarations at run time in dynamic scope
The scope rules are applied wither when a routine is called (shallow binding, default in dynamic scoping) or when a reference is created (deep binding, default in static scoping)
Deep binding is implemented through subroutine closure, which is an explicit representation of a reference environment
The binding time matters since the running program may have two instances of some object for ones that are neither local or global
This doesn’t matter for some languages, but for others, it’s very important