You could define a semantic that works like that, but no language I’m aware of actually does that. Let me try to add some background:
Purely functional programming languages basically only work with values. While you can give names to values, these can always be replaced by the corresponding value, e.g., a = 2
and b = 3 + a
means that b = 3 + 2
in every context. In particular, in such a language – e.g. Haskell – constants and functions with no arguments are just the same. Yet, since names can always be replaced by their definition no notion of mutation can be defined, i.e., what should the value of x = x + 1
be if you must be able to simply replace x
by its definition?
Most languages, allow for mutation, i.e., changing the value assigned to some name. In this case, we need to distinguish between bindings and values. Think of bindings as mappings between names and values valid in a certain context – called environment, i.e., a = 2
establishes a binding (if a
was not defined already) between the name a
and the value 2
. If a binding already exists in some environment, a = 3
would change the value assigned to the same name in this environment.
Now, what does b = a
do? In Julia this binds the value of a
(in the current environment) to the name b
. Thus, the semantics is that reading a name looks up its (current) value. The same is true in a function, but the lookup is delayed and executed when the function is called (not when its defined). What makes lexical closures so useful is that they close over the binding and not the value. This allows for multiple functions to share a binding in a single environment. A quick and dirty object system could be build on top of that:
obj = (let a = 1 # New binding in fresh environment
inc() = a += 1
dec() = a -= 1
get() = a
(; inc, dec, get)
end)
obj.get() # 1
obj.inc(); obj.inc(); obj.dec(); obj.inc();
obj.get() # 3
In this respect, the lhs and rhs of a assignment behave differently in that the lhs writes to a binding while the rhs looks up its value.
Finally, some languages – such as Rust – allow to distinguish explicitly between the binding and its values via reference semantics (also search for lvalue/rvalue):
fn main() {
let mut x = 10;
println!("One: {}", x); // prints 10
let mut z = x; // Another name for value of x
z += 2;
println!("Two: {}", x); // prints 10 again
let y = &mut x; // Another name for x itself
*y += 1;
println!("Three: {}", x); // prints 11
}