5 ms·
I think you're confusing mutation and variable reassignment?
by ramchip 1y ago
I think you're confusing mutation and variable reassignment?
- tombert 1y agoI'm saying that if I do a regular loop, something needs to explicitly mutate, either a reference or a value itself. `for (var i = 0; i < n, i++)`, for example, requires that `i` mutate in order to keep track of the value so that the loop can eventually terminate. You could also do something like: var i = new Obj(); while (!i.isDone()) { //do some stuff i = new Obj(); } There, the reference to `i` is being mutated. For tail recursion, it's a little different. If I did something like Factorial: function factorial(n, acc) { if (n <= 1) return acc; return factorial(n - 1, acc * n); } Doing this, there's no explicit mutation. It might be happening behind the scenes, but everything there from the code perspective is creating a new value. In something like Clojure, you might do something like (defn vrange [n] (loop [i 0 v []] (if (< i n) (recur (inc i) (conj v i)) v))) (stolen from [1]) In this case, we're creating "new" structures on every iteration of the loop, so our code is "more pure", in that there's no mutation. It might be being mutated in the implementation but not from the author's perspective. I don't think I'm confusing anything. [1] https://clojure.org/reference/transients https://clojure.org/reference/transients
- eru 1y agoYou could imagine a language like eg Python with a for-each loop that creates a new variable for each run through the loop body. Basically, just pretend the loop body is a lambda that you call for each run through the loop. It might make sense to think of the common loops as a special kind of combinator (like 'map' and 'filter', 'reduce' etc.) And just like you shouldn't write everything in terms of 'reduce', even though you perhaps could, you shouldn't write everything in terms of the common loops either. Make up new combinators, when you need them. For comparison, in Haskell we seldom use 'naked' recursion directly: we typically define a combinator and then use it. That often makes sense, even if you only use the combinator once.
- ramchip 1y ago> There, the reference to `i` is being mutated. That's rebinding. Mutation is when you change the state of an object. Variables are not objects. You can't have a reference (aka pointer) pointing to a variable.
- 1718627440 1y ago> You can't have a reference (aka pointer) pointing to a variable. What? int a; int * p = &a; ?
- ramchip 1y agoThat's a pointer to an integer.
- 1718627440 1y agoYes. You claimed you can't have pointers to a variable. I showed how to have a pointer to a variable of type integer.
- ramchip 1y agoIf you dereference it you get back an integer, not a variable. A variable is a name that identifies a value; the pointer points to the value, not to the name.
- mrkeen 1y agoI don't know if you're referring to a particular language's peculiarities, but it doesn't really matter. It's mutation. tombert's point (I think) is that in a functional setting, factorial(n) is always factorial(n). In an imperative setting, first it's 1, then 2, then 6, then 24, etc. Here's factorial calculated imperatively in C#. public async Task Factorial() { long primitiveResult = 1L; Wrapped<long> objectResult = new Wrapped<long>(1L); var observer = new Task(ObserveVariable); observer.Start(); for (var i = 2; i <= 15; i++) { primitiveResult *= i; objectResult = new Wrapped<long>(objectResult.Value * i); await Task.Delay(100); } observer.Wait(); return; void ObserveVariable() { while (primitiveResult != 1307674368000 || objectResult.Value != 1307674368000) { Thread.Sleep(100); } } } There are two results (one primitive and one object) to show that it doesn't matter. Maybe there's no such thing as a Reference-to-a-Variable in whatever language you have in mind, but in the above code ObserveVariable refers to a variable (while it mutates).
- gpderetta 1y agofor(int i : some-container) { do-something-with(i); } Where is the mutation?
- mrkeen 1y agoIt's implied by void-returning do-something-with(...). Otherwise you're just burning cycles and a good compiler should dead-code eliminate the whole loop. (see https://news.ycombinator.com/item?id=44873488 https://news.ycombinator.com/item?id=44873488)
- gpderetta 1y agoWe are discussing mutation in the loop itself, but sure: for(int x: container) { yield frob(x); }
- mrkeen 1y agoNope. Loops are unnecessary unless you have mutation. If you don't mutate, there's no need to run the same code again: if the state of the world did not change during iteration 1, and you run the same code on it again, the state of the world won't change during iteration 2.