31 ms·
You're just shadowing the previous binding with a new one, not actually mutating anything[1]: λ> let foo = 1 λ> let foo = 2 λ> foo => 2 could
by nimih 7y ago
You're just shadowing the previous binding with a new one, not actually mutating anything[1]:
λ> 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.
- imtringued 7y ago> 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. 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.