As we have already seen, all Erg variables are immutable. However, Erg objects have the concept of mutability. Take the following code as an example.

a = [1, 2, 3]
a = a + [4, 5, 6]
print! a # [1, 2, 3, 4, 5, 6]

The above code cannot actually be executed by Erg. This is because it is not reassignable.

This code can be executed.

b = ![1, 2, 3]
b.concat! [4, 5, 6]
print! b # [1, 2, 3, 4, 5, 6]

The final result of a, b looks the same, but their meanings are very different. Although a is a variable that represents an array of Nat, the objects pointed to in the first and second lines are different. The name a is the same, but the contents are different.

a = [1, 2, 3]
print! id! a # 0x000002A798DFE940
_a = a + [4, 5, 6]
print! id! _a # 0x000002A798DFE980

The id! procedure returns the address in memory where the object resides.

b is a Nat "dynamic" array. The content of the object changes, but the variables point to the same thing.

b = ![1, 2, 3]
print! id! b # 0x000002A798DFE220
b.concat! [4, 5, 6]
print! id! b # 0x000002A798DFE220
i = !0
if! True. do!
    do!!() # or i.add!(1)
    do pass
print! i # 1

! is a special operator called the mutation operator. It makes immutable objects mutable. The behavior of objects marked with ! can be customized.

Point = Class {.x = Int; .y = Int}

# In this case .x is made mutable and .y remains immutable
Point! = Class {.x = Int!; .y = Int}
    inc_x! ref!(self) = self.x.update! x -> x + 1

p = Point!.new {.x = !0; .y = 0}
print! p.x # 1


Unlike variables, constants point to the same thing in all scopes. Constants are declared with the = operator.

PI = 3.141592653589
match! x:
    PI => print! "this is pi"

Constants are identical in all scopes below the global and cannot be overwritten. Therefore, they cannot be redefined by =. This restriction allows it to be used in pattern matching. The reason why True and False can be used in pattern matching is because they are constants. Also, constants always point to immutable objects. Types such as Str! cannot be constants. All built-in types are constants because they should be determined at compile time. Types that are not constants can be generated, but they cannot be used to specify a type and can only be used like a simple record. Conversely, a type is a record whose contents are determined at compile time.

Variable, Name, Identifier, Symbol

Let's clear up a few terms related to variables in Erg.

A Variable is a mechanism to give an object a Name so that it can be reused (or point to that Name). Identifier is a grammar element that specifies a variable. A symbol is a grammatical element, a token, that represents a name.

Only non-symbolic characters are symbols, and symbols are not called symbols, although they can be identifiers as operators. For example, x is an identifier and a symbol. x.y is also an identifier, but it is not a symbol. x and y are symbols. And even if x were not tied to any object, x would still be a Symbol and an Identifier, but it would not be called a Variable. Identifiers of the form x.y are called Field Accessors. Identifiers of the form x[y] are called Subscript Accessors.

The difference between a variable and an identifier is that if we are talking about a variable in the sense of Erg's grammatical theory, the two are in effect the same. In C, types and functions cannot be assigned to variables; int and main are identifiers, but not variables (strictly speaking, they can be assigned, but there are restrictions). However, in Erg, "everything is an object". Not only functions and types, but even operators can be assigned to variables.