Accessors
Accessors are the central means of reading and writing variable data in Ranty.
Accessors are contained within a pair of angle brackets (< >).
They have several uses:
- defining new variables
- getting/setting variable values
- accessing elements in collections
Accessors have three subtypes: definitions, setters, and getters.
Definitions
A definition creates a new variable in the current scope.
They are denoted by placing a $ symbol before the variable name.
It is optional to assign a value in a definition. You can leave the assignment part out and it will be initialized to nothing.
# Define a variable `name` but initialize it to <>
<$name>
# Define a variable `name` and assign it the string "Nick"
<$name = Nick>
Lazy definitions
Use ?= instead of = to define a variable or constant whose initializer should be deferred until the value is first accessed.
<$config ?= [load-config]>
# Nothing has run yet
<config> # [load-config] runs here
<config> # cached result is reused
Lazy definitions use call-by-need semantics:
- the initializer does not run when the definition is created
- the initializer runs the first time the binding is read, indexed, keyed, or called
- the successful result is memoized and reused for the rest of the binding's lifetime
If you overwrite a lazy variable before it is forced, the original initializer is discarded without running:
<$secret ?= [load-secret]>
<secret = demo>
<secret> # -> demo
Constants can also be lazy:
<%rules ?= [load-rules]>
<rules> # forces once
<rules> # reuses cached value
<rules = x> # error: constants still cannot be reassigned
Lazy initializers capture local variables by reference, just like closures:
<$value = 1>
<$lazy ?= <value>>
<value = 2>
<lazy> # -> 2
Any access rooted at the lazy binding forces it first, including child access and function lookup:
<$items ?= [make-items]>
<items/0>
<$handler ?= [make-handler]>
[handler]
Self-referential lazy bindings are not allowed and raise a runtime error when forced:
<$x ?= <x>>
<x> # runtime error: LAZY_BINDING_CYCLE_ERROR
Note:
?=is only valid on definitions. In getters,?still starts a fallback expression.See also Lazy parameters and Closures.
Setters
A setter modifies an existing variable or value.
# Define a variable
<$name = Bob>
# Overwrite value on existing variable `name`
<name = Susan>
When a setter targets a map key, it always writes to that map directly. If the same key also exists on a prototype, the setter creates or updates a local key instead of mutating the prototype.
Along with setting variables, setters can also write to specific elements of collections.
<$numbers = (: 1; 2; 3)>
<numbers/0 = 4> # list is now (: 4; 2; 3)
Getters
A getter retrieves some value and prints it to the output.
Attempting to retrieve a variable that does not exist causes a runtime error.
# Get value of `name` (note the lack of '$')
<$name = Robin>
My name is <name>.\n # Prints "My name is Robin."
Map getters can also resolve inherited members through a map's prototype chain. For the detailed rules, see Map Prototypes.
Attribute keyword accessors
Some runtime attributes also expose dedicated accessor forms:
<@rep>,<@sep>,<@sel>,<@mut>,<@step>, and<@total>read the current attribute state.<@rep = expr>,<@sep = expr>,<@sel = expr>, and<@mut = expr>write the current attribute frame.
These forms are specialized syntax, not general access paths. They do not support globals,
descoping, fallbacks, or compound assignment. @step and @total are read-only and cannot be assigned.
Multi-part accessors
To aid readability, Ranty also allows you to place several access operations in a single accessor block. Simply end each operation with a semicolon; the final semicolon is optional and may be omitted.
<$first-name = John; $last-name = Smith; $full-name = <first-name>\s<last-name>;>
Variable scope
Variables only live as long as the expression or block in which they are defined.
Blocks, function arguments, setter expressions, dynamic keys, and function bodies are all examples of variable scopes. As soon as a scope is finished running, all variables defined within it are discarded.
Child scopes
Child scopes inherit variables from parent scopes. In addition, they may define their own variables.
{
<$a = 1>
{
<$b = 2>
[add: <a>; <b>] # Outputs "3"
}
}
Shadowing
Variables in a parent scope can be temporarily hidden ("shadowed") by defining a variable of the same name in a child scope.
When the child variable goes out of scope, the shadowed parent variable will once again become accessible.
# Define variable `a` in parent scope
<$a = foo>
a is <a>\n
{
# Define another variable `a` in child scope
# Parent variable is not affected
<$a = bar>
a is <a>\n
}
# Parent variable takes over after child scope exits
a is <a>
##
Output:
a is foo
a is bar
a is foo
##
Constants
Constants are much like variables in that they are scoped the same way and store a value. Unlike variables, however, constants are immutable: they can only be assigned within a definition, and cannot be redefined.
The syntax to define a constant is simple: where variable definitions use $, constant definitions use %:
# Variable definition
<$mutable-value = 123>
# Constant definition
<%constant-value = 123>
<mutable-value = 456> # works as expected
<constant-value = 456> # error
Constants can still be shadowed by child scopes.
<%foo = 123>
{
<$foo = 456> # this is valid since we're not reassigning ^foo
}
By-ref constants have interior mutability
Storing a by-ref type, such as a list or map, in a constant does not prevent its contents from being changed; it only guarantees that the reference stored by the constant cannot change.
# Create a constant list
<%special-list = (: 1; 2; 3)>
# Modifying the contents is still allowed
[push: <special-list>; 4] # list now contains (: 1; 2; 3; 4)
# Swapping out the list itself is not allowed
<special-list = (: 7; 8; 9)> # error!