Scopes

Scopes are one of the most important concepts in Stack as they set Stack apart from traditional concatenative languages. We have put a ton of thought into how they work and how they should behave.

What is a Scope?

Similar to other languages, a scope is a collection of symbol-expression pairs. When a variable is defined, it is added to the current scope. When a variable is called, it is looked up in the current scope (by the purification step).

The Main Scope

The main scope is the top-level of your program. It is the first scope that is created when you start Stack and is the outermost scope.

In the variable section, all examples were in the main scope. Though, the behavior of those examples doesn't change when inside of a scope other than main (such as when within a function).

Creating Scopes

When a function is called, a new scope is created that lives for as long as the function is running. When the function is done, the scope is destroyed.

Scoping Rules

Isolation

Inner scopes are isolated from their outer scope. This means that variables defined in the inner scope are not accessible from the outer scope and will be destroyed when the inner scope is destroyed (unless they are referenced, see closures).

'(fn 0 'a def) call

a
;; Throws an error because `a` is not defined in the main scope

Access and Inheritance

This newly created scope has access to the outer scope. It can read and write to the existing variables in the outer scope. However, if a variable is defined in a inner scope, is will not be accessible from the outer scope.

Therefore, inner scopes have full access to existing variables of the outer scope, but cannot introduce new variables into that (outer) scope.

Getting:

0 'a def

'(fn a) call

;; Pushes 0 to the stack
;; [] -> [0]

Setting:

0 'a def

'(fn 1 'a set) call

a
;; Pushes 1 to the stack
;; [] -> [1]

Shadowing

When a variable is defined in a inner scope with the same name as a variable in the outer scope, the inner scope's variable will "shadow" the outer scope's variable. This means that the inner scope's variable will be used instead of the outer scope's variable. So, the inner scope has access to its own variable while the outer scope's variable is still accessible from within the outer scope.

0 'a def

'(fn 1 'a def a) call
;; Pushes 1 to the stack
;; [] -> [1]

a
;; Pushes 0 to the stack
;; [] -> [0]

Closures

When a variable is referenced in a inner scope, it will be kept alive in the outer scope. This is called a closure and is similar to the behavior in languages such as JavaScript.

;; This function isn't lazy, so it will run automatically
(fn
  0 'a def

  '(fn a)
)
;; (returns the inner function)
;; Pushes `(fn a)` to the stack
;; [] -> [(fn a)]

;; Call the inner function
call
;; Pushes `0` to the stack
;; [(fn a)] -> [0]

As you can see, the inner function still has access to the outer scope's variable a even though the outer function finished executing.