Scope
A scope is akin to a dictionary, where values are associated with keys.
Definition
Scopes are defined using curly brackets {}, as shown below:
myCoolScope = {
place = 'here'
when = 'now'
}
Scopes store entries in alphabetical order, a characteristic that becomes apparent when mapping over a scope.
Access
There are three ways you can directly access entries inside a scope.
Dot syntax
myCoolScope.place # output: 'here'
Get syntax
# assuming prop = 'place'
myCoolScope(prop) # output: 'here'
In both methods, if the property is not present, null is returned. If the outer scope is not found, an error is raised.
Optional chaining syntax
Use the question-dot ?. operator to safely chain potentially non-existent outer scopes:
nonExisting?.prop # returns null
The optional chaining syntax does not raise an error when the outer scope is null.
Operations
==equal!=not equal+addition (merge effect)-subtraction (difference effect)&logical AND|logical OR
logical AND/OR evaluate empty scopes as
false, otherwisetrue
Scope addition (merge)
The second operand acts as a patch for the first operand:
x = { a = 1, b = 3 }
y = { b = 2 }
x + y # results in { a = 1, b = 2 }
y + x # results in { a = 1, b = 3 }
values from the second operand replace those from the first
Scope subtraction (difference)
Subtraction removes elements from the first operand that are identical to those in the second operand:
x = { a = 1, b = 3 }
y = { a = 1 }
x - y # results in { b = 3 }
only values that are exactly identical are removed
Scoped Blocks
Scoped Blocks in FatScript allow for executing statements within the context of a specific scope:
object.{
# Statements executed in the context of 'object'
}
Here, object is the target scope. Within the block, you can directly access and modify object's properties.
Features
- Isolation: entries declared within a Scoped Block are local to that scope and do not affect the outer scope
- Outer Scope Access: Scoped Blocks can access entries from the outer scope
Example
x = {}
x.{
a = 5 # 'a' is now a property of 'x'
b = a + 3 # 'b' is also a property of 'x'
}
Scope interactions
FatScript resolves names dynamically, walking the live call stack — there is no lexical scoping. This section explores these mechanisms, including name resolution order, assignment nuances, increment/decrement behaviors, and the innovative use of the += operator for boolean toggling.
Name resolution
A free name is resolved against the frames that are live when the code runs:
- the current frame (locals and parameters)
- the method's bound scope, when it has one (e.g. the module where it was declared, pushed at call time)
- the enclosing live frames of the call stack, nearest first
- the global (root) scope, as last resort
A dotted call like holder.method(...) pushes the holder scope, which is why module functions see their module siblings, and why arguments of such calls can reference members of the holder directly.
since v4.6.0, methods declared at the top level of a program no longer bind the global scope: root bindings resolve as last resort, so params and locals of enclosing functions win over same-named top-level entries, and curried top-level methods read root bindings live
Assignment
The assignment operator (=) copies values from outer scopes into the current scope, defining a new value:
~ n = 1
x = {}
x.{ ~ n = n } # now x.n == 1, and x.n is independent from root.n
x.{ c = n } # has similar effect, however 'c' is immutable
the same concept applies to code running on a method scope
Capturing external values
When declaring a scope inline, you can "capture" values from the outer scope simply by naming them, without re-assigning:
a = 5
b = 6
myScope = { a, b = 7 }
# myScope == { a = 5, b = 7 }
Here, a is copied from the outer context at the moment the scope is created. This technique is useful when you want to build scopes or return objects that reference parameters or local values in a concise way.
Caveat
Using ~ n = n + 1 inside a block or method adds a new 'n' in the current scope, initialized with the value of n + 1 from the nearest enclosing scope, without altering the outer n.
Incrementing and decrementing
Increment (+=) and decrement (-=) operations, interact with variable scoping in a different way. These operations search for the nearest instance of a variable, starting from the current scope and moving outward recursively, and then modify that instance directly.
~ outerN = 1
fn = -> {
outerN += 1 # targets and increments 'outerN' in the outer scope
}
Auto-initialization with +=
FatScript also provides a special behavior regarding increment operator (+=). If the entry doesn't exist, increment works as a regular assignment as if you had written the following for n += 1:
n == Void ? n = 1 : n += 1
The auto-initialization feature can be particularly useful when used in combination with dynamic entries for dynamic programming.
this feature is exclusively available for increment operator, decrement can't initialize non-existent values
Boolean toggling with +=
Generally, booleans don't allow addition operations. FatScript, however, extends the += operator's functionality to boolean types, allowing for an intuitive toggle mechanism within inner scopes.
The expression flag += !flag effectively toggles the boolean value, even when flag is defined in an outer scope.
in the particular case of booleans, the only distinction between
=and+=is scoping
Other compound assignment operators
Similarly, other compound assignment operations such as *=, /=, %=, and **= are supported by numeric types and respect the same scoping rules that apply to increment and decrement operations.