Non-lexical Bindings
Lexical bindings are variables bound to a specific scope. They live and die in that context. Non-lexical bindings are thus the opisite. In most languages, the only non-lexical binding is the dreadful Global Variable. However, lexical bindings pin code in place.
The dataflow of variables has to be carefully unwound before code can be relocated. This process can be haphazard and tedious if many variables are involved. Furhtermore, they can result in heavily duplicated logic just because the variable bindings can't be abstracted.
Luckily, some projects have recongized the value of global variables, but have tamed some of their undesired parts.
Current Objects
Dyon is a somewhat unknown Rust scripting language. There are many features unique to it, some coming from it's Rust origin. However, a notable one is how it manages dynamic scope.
In Dyon, dynamic scope is managed using "Current Objects". This feature additionally replaces the usage of global state.
fn main() {
~ list := [1, 2, 3]
print_list()
}
// Get the current object with name `list`.
fn print_list() ~ list {
for i { println(list[i]) }
}
Current Objects allow for implicit variable passing. This feature lends well to writing compose able units of code. Furthermore, Current Objects obey the lexical scoping of Dyon.
fn main() {
~ a := 2
foo() // prints `2`
{
~ a := 3
foo() // prints `3`
}
foo() // prints `2`
}
fn foo() ~ a {
println(a)
}
Mirth's stack label
In April of 2025, the concatenative programming language Mirth added "Stack Labels" to the language. Stack Labels allows for the construction of multiple named stacks to be shared alongside the main data stack.
This allows for generalizing code with varible in a way that is impossible with lexical bindings.
def sweep-grid(f) [(*a x:Int y:Int -- *a x:Int y:Int) *a -- *a] {
0 >x 0 >y
SAND_SIZE Nat.NatUnsafe count(>Int !x
SAND_SIZE Nat.NatUnsafe count(>Int !y f)
)
x> drop y> drop
}
# Logic for moving the sand
def update-sand! [+Game(+SandState) -- +Game(+SandState)] {
sweep-grid(
@x SAND_SIZE 1- swap - >x
@y SAND_SIZE 1- swap - >y Position move-sand!
)
}
# Handle drawing sand
def draw-sand! [+Game(+SandState) -- +Game(+SandState)] {
sweep-grid(
@x >x @y >y Position is-sand? then(
# This code takes advantage of the fact Stack
# Labels define named stacks. Thus we can mask
# the current values of `x` and `y`
@x CInt >x @y CInt >y
+backend:sdl:SDL_RenderDrawPoint
)
)
}
Having multiple named stacks massively improves the ergonomics of higher-level catlangs. Lexical variables are infamous for ruining the concatenative property of catlangs.
Okay, fine. "Infamous" is just what we say they are. We're the anti-lexical variable goblin on the catlang discord. But they really do just ruin the experience of refactoring code. Additionally, in languages like Factor, they explode your code, too.
Dawn
Dawn is (tho, sadily it's likely "was") an idea for a concatenative programming language similiar to what Mirth is now. In one of its blog post, it describe a multistack concatenative calculuse. In that article, it included an interesting point about multistacks.
Global variables are a useful feature that is available in many existing programming languages. Global variables allow the user to access a value from many different functions without having to explicitly thread the value through every function call. Unfortunately, global variables have some drawbacks. Most importantly, accessing a mutable global variable breaks referential transparency, which is a desirable property that makes programs easier to understand, analyze, test, and optimize. And in statically typed languages with global variables, the type of a function (typically) does not reflect whether or not it accesses any global variables, which exacerbates the problem.
In a multistack concatenative calculus, we can use the global availability of arbitrarily named stacks to gain the benefits of global variables without encountering these drawbacks. Namely, we do not need to explicitly thread values through each function call. Instead, every value is available somewhere on the value multistack. In the UMCC, there isn’t much of a benefit, since letting any term access or alter any value isn’t particularly different from letting any function access or alter any global variable. However, once we add a static type system, the type for every term will reflect which values it touches. As a result, all terms in such a multistack concatenative calculus would be referentially transparent.
Exerp from "Foundations of Dawn: The Untyped Multistack Concatenative Calculus" by Scott J Maddox
Nova
Nova is a small multistack rewriting language. All data is stored within named stacks. Stacks are operated on using rules describing how cards within that stack of shuffled, destroyed, and transvered between other stacks. All stacks in Nova are global.
|| :: sort items :items:
. red shoe
. green shirt
. red hat
. blue socks
. green pants
. blue underware
|:: sort items? :items: red $what|
:red: $what
|:: sort items? :items: green $what|
:green: $what
|:: sort items? :items: blue $what|
:blue: $what
|:: sort items|
:: put items back in order
|:: put items back in order? :red: $what|
:items: red $what
|:: put items back in order? :blue: $what|
:items: blue $what
|:: put items back in order? :green: $what|
:items: green $what
|:: put items back in order|
I've been helping out a bunch with this one. It's something I think is very exciting for the future of programming.
Coeffects
Both Current Object and Stack Labels encode a depdency on the surrounding enviroment. This can be generalized into Coeffects.Information on Coeffects can be found in Tomas Petricek's wonderfuly interactive article "Coeffects: Context-aware programming languages".
Coeffects are Tomas Petricek's PhD research project. They are a programming language abstraction for understanding how programs access the context or environment in which they execute.
The context may be resources on your mobile phone (battery, GPS location or a network printer), IoT devices in a physical neighborhood or historical stock prices. By understanding the neighborhood or history, a context-aware programming language can catch bugs earlier and run more efficiently.