Posted by azhenley 3 days ago
Similar to how "tail recursion can (usually) be lifted/lowered to a simple loop...", immutability from language statements can often be "collapsed" into mutating a single variable, and there may be one or two "dances" you need to do to either add helper functions, structure your code _slightly_ differently to get there, but it's similar to any kind of performance sensitive code.
Example foo(bar(baz(), bar_alt(baz_alt(), etc...))) [where function call nesting is "representing" an immutability graph] ...yeah, that'd have a lot of allocations and whatever.
But: foo().bar().bar_alt().baz().baz_alt().etc(...) you could imagine is always just stacking/mutating the same variable[s] "in place".
...don't get hung up on the syntax (it's wildly wrong), but imagine the concept. If all the functions "in the chain" are pure (no globals, no modifications), then they can be analyzed and reduced. Refer back to the "Why SSA?" article from a week or two ago: https://news.ycombinator.com/item?id=45674568 ...and you'll see how the logical lines of statements don't necessarily correspond to the physical movement of memory and registers.
> Having all the intermediate calculations still available is helpful in the debugger
    classList = ['highlighted', 'primary']
    if discount:
        classList.append('on-sale')
    classList = ' '.join(classList)
And not having to think about e.g. `const` vs `let` frees up needless cognitive load, which is why I think python (rightly) chose to not make it an option.1.
    classList = ['highlighted', 'primary']
        .concatif(discount, 'on-sale')
        .join(' ')
2.    classList = ' '.join(['highlighted', 'primary'] + (['on-sale'] if discount else []))
3.    mut classList = ['highlighted', 'primary']
    if discount:
        classList.append('on-sale')
    classList = ' '.join(classList)
    freeze classList
4.    def get_class_list(discount):
        mut classList = ['highlighted', 'primary']
        if discount:
            classList.append('on-sale')
        classList = ' '.join(classList)
        return classList
    classList = get_class_list(discount)    (table.concat [:highlighted :primary (if discount :on-sale)] " ")Anyway the outcome of trying to avoid mutation means instead of simply setting player.score you get something like player = new Player(oldPlayerState, updates). This is of course slow as hell. You're recreating the entire player object to update a single variable. While it does technically only mutate a single object rather than everything individually it's not really beneficial in such a case.
Unless you have an object with a lot of internal rules across each variable (the can't be 'A' if 'B' example above) it's probably wrong to push the mutation up the stack like that. The simple fact is a complex computer program will need to mutate something at some point (it's literally not a turing machine if it can't) so when avoiding mutation you're really just pushing the mutation into a higher level data object. "Avoid mutations of data that has dependencies" is probably the correct rule to apply. Dependencies need to be bundled and this is why it makes sense not to allow 'A' in the above example to be mutated individually but instead force the programmer to update A and B together.
Wouldn't this be an easy task for SCA tool e.g. Pylint? It has atleast warning against variable redefinition: https://pylint.pycqa.org/en/latest/user_guide/messages/refac...
A lot of code needs to assemble a result set based on if/then or switch statements. Maybe you could add those in each step of a chain of inline functions, but what if you need to skip some of that logic in certain cases? It's often much more readable to start off with a null result and put your (relatively functional) code inside if/then blocks to clearly show different logic for different cases.
  if cond:
      X = “yes”
  else:
      X = “no”
X is only ever assigned once, it’s actually still purely functional. And in Rust or Lisp or other expression languages, you can do stuff like this:  let X = if cond { “yes” } else { “no” };
That’s a lot nicer than a trinary operator!    let x: Int
    if cond {
        x = 1
    } else { 
        x = 2
    }
    // read x here  let x = if cond { 1 } else { 2 }The value (pun intended) of the latter is that once you’ve arrived at a concrete result, you do not have to think about it again.
You’re not defining a “variable”, you’re naming an intermediate result.