---
> kaos> num a = 5
> kaos> a = 7
> kaos> print a
> 7
Doesn't that mean a is mutable by default?
---
> kaos> num a = 5
> kaos> a = 7
> kaos> print a
> 7
Doesn't that mean a is mutable by default?
It would be mutable if it allowed something like this:
> a = 5
> a.add(1)
> print a
6
Note that numbers are immutable in most languages anyway. Arrays, hashmaps, and sometimes strings are the data types that are frequently mutable.> 3 = 5
> print 3
5
The end
But given that it's not an object oriented language, how could any variable be mutable given your example/definition?
How could you mutate a variable in a way that this language disallows?
I presume something like this:
array a = [1, 2, 3]
a.append(4)
Names are mutable, but the values they point to are not.> Arrays and the elements of arrays are also immutable
I guess the docs need a bit of rewording. Forcing a deep-copy on variable assignment does not mean the variable itself is immutable.
https://www.amazon.com/Purely-Functional-Structures-Chris-Ok...
> Chaos language is not object-oriented. So everything is done by functions and data types.
a[0] = 1
Would be a better example. Appending to an array could involve creating a new array and reassigning it to ‘a’. function add(x, y) {
return x + y
}
a = 5
add(a, 1)
print(a)
A language printing 6 is pass by reference. A language printing 5 is pass by value.
Both would print 6 for this code a = add(a, 1)
print(a)
However a language like Erlang would end with an error when trying to reassign a new value to a. function add(x, y) {
x = x + y
return x
} GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help
Prelude> a = 5
Prelude> a = 7
Prelude> print a
7
But yeah, it doesn't seem like there's any support for first class functions either, so perhaps just a bit too much creative license in marketing? λ> let foo = 1
λ> let foo = 2
λ> foo
=> 2
could be rewritten[2] as λ> let foo = 1 in ( let foo = 2 in foo )
=> 2
which makes the semantics more clear.This idiom is also quite common in Clojure, another famously immutable-first language:
(let [foo 1
foo 2]
foo)
;; => 2
In my opinion (predominantly informed by my experience with those two languages contrasted with the usual suspects from mutable-OOP-land), the benefit of immutability isn't what's happening in your own local scope, since it's typically quite easy to track what your immediate context is doing to a variable. Instead, it's the language-enforced promise that the only changes to local values can come from local actions: your function and method calls can never have spooky side effects, nor can other threads if you're in some hellish concurrent environment.In this light, Chaos's apparent syntactic sugar of
a[15] = 44
to mean (using some hand-wavey pidgin) a = a.updateAt(15,44)
seems quite reasonable and fully in the spirit of immutability as a meaningful language feature.[1] In my scratchwork project, my repl actually yells at me with an annoying warning about this:
<interactive>:8:5-7: warning: [-Wname-shadowing]
This binding for ‘foo’ shadows the existing binding
defined at <interactive>:6:5
[2] I believe that the haskell repl actually functions like a do-block, so the pedantically correct desugaring possibly involves lambdas and bind, but that's not really an interesting distinction here IMO and makes the example less clear.From my experience any sufficiently badly written code is hard to understand even in the local scope. The bar for hard to understand code isn't that high because the complexity of well written code is very close to zero.
Most code that avoids reassignment can be read from top to bottom. Code with local mutation can require backtracking and then the complexity can start exploding but that doesn't necessarily apply to code with mutation across functions. A simple list.add() doesn't cause anyone's brain to melt.
A classic is that someone writes a for loop like this: for(;i < 10; i++). You now need to go back and check what the start value of i is even though 99% of the time it is 0. Then you need to check if the counter is used for anything after the loop has exited because sometimes a loop is just trying to find the first element that matches a predicate. Now imagine if the i variable gets reused by a second loop. You now must check if the i variable is resuming the loop at the same position by backtracking. Every single line of code has the potential to amplify the complexity of the entire function.
All that meaningless complexity has no reason to exist. It doesn't provide any value and it doesn't cost anything to avoid it.
int a = 5;
a = 7;