8 ms·
Immutability is not enough (2016)
- einpoklum 5y agoIt's almost as if the author is telling us that "There is no silver bullet". Now where have I heard that already? ... http://worrydream.com/refs/Brooks-NoSilverBullet.pdf http://worrydream.com/refs/Brooks-NoSilverBullet.pdf
- jt2190 5y agoReposting this, as it was mentioned by belter [1] in another discussion on Immutability changes everything (2019) [2] and I thought it was interesting enough for another visit. Previous discussion five years ago: https://news.ycombinator.com/item?id=11388143 https://news.ycombinator.com/item?id=11388143 [1] https://news.ycombinator.com/item?id=27640700 https://news.ycombinator.com/item?id=27640700 [2] https://news.ycombinator.com/item?id=27640308 https://news.ycombinator.com/item?id=27640308
- JadeNB 5y ago> Reposting this, as it was mentioned by belter [1] in another discussion on Immutability changes everything (2019) [2] and I thought it was interesting enough for another visit. "Immutability changes everything" (https://queue.acm.org/detail.cfm?id=2884038 https://queue.acm.org/detail.cfm?id=2884038) also seems to be from 2016.
- weego 5y agoI don't understand how the writer didn't realise somewhere during writing this that they're conflating immutable state concerns and higher-level state validation in logic loops. Collision detection validation and the game loop, to use the example, has absolutely nothing to do with what kind of data structure you're using, and is absolutely not what functional programming was 'supposed to help us avoid'.
- dkarl 5y agoI don't know if the author meant to, but he describes a problem that people run into with concurrent systems, and which creates a lot of real-life bugs if people don't anticipate it and design for it. In the author's case, the solution is trivial because it can be solved by specifying the order of updates, but in concurrent systems that isn't always an option. Let's say instead of Manuel the Carpenter's position on the screen, we're updating the status of Monte the Money Launderer's application for a line of credit. The CreditCheck system receives an external credit report and updates Monte's application state to indicate he has passed the credit check. The Approval system sees the credit check result, sees that all other conditions have been met, and updates the application state to Approved. At the same time, a loan officer who has received a call from the FBI about Monte uses the Control system to put a manual Hold on the loan. If the concurrent updates from the Approval and Control systems are combined naively, the application can end up in the state of Approved and Hold simultaneously. The loan officer, seeing that the Hold has been successfully placed on the application, believes that Monte has been blocked, but in fact Monte is able to access the Approved line of credit. Like the problem described by the author, this is a pretty basic problem that most HN readers would design for from the start, but the title "Immutability is not enough" should (theoretically) select for readers who are a little bit surprised by it.
- deleted 5y ago[deleted]
- speed_spread 5y agoFor a second I thought this was the title of a new James Bond.
- orangepanda 5y agoNo Mr Bond, I expect you to die();
- rowanG077 5y agoHow is immutability supposed to help mitigate these bugs? This seems like the programmer just not encoding the allowed state change accurately. In the real world(tm) you should always update the state either through functions that make sure you are not reaching undesirable states or encode you state at the type level. This is completely orthogonal to immutability though.
- solipsism 5y agoIt's rather a non sequitur, as no one ever said immutability was enough. You also need design, rigor, error handling, common sense, some caffeine, testing, and fast builds. All together... that's enough!
- catlifeonmars 5y ago> fast builds Interestingly, immutability and fast builds are often closely related, because immutability (in build systems) can be used to ensure referential transparency, which makes it trivial to implement caching of intermediate build artifacts.
- NaturalPhallacy 5y agoI just want to point out that this is really funny to see at the same time on hn. This article, and one from 7 hours ago: Immutability Changes Everything (2016) (acm.org): https://queue.acm.org/detail.cfm?id=2884038 https://queue.acm.org/detail.cfm?id=2884038 Posted here: https://news.ycombinator.com/item?id=27640308 https://news.ycombinator.com/item?id=27640308
- tyingq 5y agoYes, but it's not a coincidence. Someone read https://news.ycombinator.com/item?id=27640700 https://news.ycombinator.com/item?id=27640700 and thought it was worth posting.
- lmilcin 5y agoI am little bit confused. Why do you expect immutability to solve all your problems? Immutability is a tool. Like every tool, it has its limits. Its benefits are finite. Expecting it to solve all problems is silly -- there does not exist a single technique that can solve all problems in every circumstances. Rather than looking for a silver bullet it is better to study various techniques, their pros, cons and applicability and build varied repertoire of solutions you know well enough to be able to predict the results and achieve high chance of success. Don't discount techniques just because they are old and have problems. If something was popular in the past it is likely it has some merit to it -- try to understand it rather than dismiss it because it is not new and shiny.
- hypertele-Xii 5y ago> there does not exist a single technique that can solve all problems in every circumstances. The Universal Algorithm. Wouldn't that be something.
- zmmmmm 5y ago> Why do you expect immutability to solve all your problems? Immutability (and functional programming more generally) are often sold as solving issues related to state management. It's not unreasonable for someone to write about where the limits are to that. Especially when many of those limits overlap closely to the traditional types of problems we see in imperative / mutable state systems.
- leoc 5y agoToday, on episode 42 of /Putting Things In-Band Doesn't Make Them Go Away/ ...
- Aaargh20318 5y agoWhile making the state immutable made it more visible what code was affecting the state, the end result is still multiple pieces of code directly affecting what is essentially a global state. Sure, a copy is passed from one function to the next, but there is still one 'current' state and everything is messing with it directly. The real problem here is not the mutability of the state, it is the ownership of it. Who is responsible for keeping the state internally consistent ? In this code the answer is: no one. To solve his problem, there needs to be a clear owner of the state, and that code should be the only code directly affecting the state and and be responsible for keeping the state internally consistent. Wether this 'owner' is a collection of functions that operate on a global state in a language like C, or on a state passed in and returned, or an object in an OO language, or whatever. Doesn't really matter. For example. Moving the character and collision detection should not be two separate function that affect the state but that can be called separately (or in the wrong order) and keep the system in an incorrect state. Only the code responsible for modifying the state should do so, and it should guarantee to leave it in a correct state on returning. Moving without collision detection can leave the system in an incorrect state and thus should not even be a function that exists. When designing a system this is always something I keep in the back of my head: who is responsible for what ? Once you have that clear, things become much easier.
- MuffinFlavored 5y ago> Who is responsible for keeping the state internally consistent ? What does this even mean? > The real problem here is not the mutability of the state, it is the ownership of it. 1. Start with an initial state 2. Pass a copy of that initial state into a function, let it return a slightly modified version of that initial state 3. Pass that slightly modified version into another function, wash rinse repeat The only ownership that is happening that needs to be worried about is the parent function passing a copy of state to a child function for the duration of that call, right?
- nijave 5y agoI think the parent post is suggesting a lack of cohesion. E.g. the things changing state are sprinkled around in too many different places--it makes it hard to reason about The solution really depends on the design pattern so the message tends to be fairly vague. From an OOP perspective, maybe more state modifiers should be instance methods are at least belong in the same namespace
- FpUser 5y agoIMHO this is atrocious waste of computer resources. Especially if the stage gets big and must survive the duration of application. To me it looks like crusade for the sake of idea anything else be damned. There are multiple better ways to deal with the state. For example more sane approach would involve single owner of the main state which can serve / update particular slices of said state and has the ability to broadcast state change events to subscribers.
- mmazing 5y agoModern developers have the luxury of computing power, but you're right, it can only take you so far. I've heard far too many people say things like "[hard drives/memory/cpu] are fast, who cares about how efficient this is?", "we'll just scale up some more instances to deal with the inefficiency.", "pfft, it's 2015, the internet is fast, who cares if network calls are inefficient, microservices are the future!" Lol. Kills me.
- oblak 5y agoThat's only the second immutability (also from 2016) thread on first page. What are the chances? Is immutability a new religion?
- BoiledCabbage 5y agoThe post while interesting - puts forward a bit of a false argument. And the flaw is seen in the title: "Immutability is not enough" Enough for what? If the author had finished the title, it'd be pretty clear that what they're arguing is kinda obvious. "Immutability is not enough to solve every ordering / dependency problem in programming" I'm trying to come up with the most charitable completion of the title, but written out they all sound pretty patronizing. (maybe someone else can do better than me?) Now all of that said. It is a great example of an article for someone to see the benefits of transitioning from inline imperative updates. To better factored code, immutable data and explicit passing of dependencies. And then finally to introduce the motivation and benefits of an effect system. For that I applaud the author.
- layoutIfNeeded 5y agoTldr: a buggy imperative program can be mechanically translated into an equally buggy functional program.
- didibus 5y agoThis is a common misconception, functional code is order independent in its evaluation model, but if you are modeling order dependent operations, it still lets you specify order for them, and if you specify the wrong order for what you're doing it'll do the wrong thing. So you can think of it as it solves accidental complexity, but leaves essential complexity still for you to solve. A very simple example is: 30 - 2 * 10 This is order dependent, but it's part of the essential complexity of the problem at hand. Now where accidental complexity would creep in is with something like: position - 2 * position + 10 Now first we have the essential complexity, what order is the behavior we want? Let's say here we want: (position - 2) * (position + 10) In imperative we might do: position = 10 position.minus(2) position.plus(10) position.times(position.value) And hopefully you're already seeing the accidental complexity created by the imperative approach. This doesn't work, and makes no sense. It isn't just that the order of the operations we're modeling is defined wrongly, in fact you could say it's defined correctly, we want to substract, add and then multiply. But in this case it's the mutable state itself which makes things more complex then they need to be. So in imperative you'd have to do: position = 10 firstPosition = position.value secondPosition = position.value firstPosition.minus(2) secondPosition.plus(10) position = firstPosition.times(secondPosition) position.value That's a lot of added complexity due to us having to manage the fact that the state is mutable. We need three memory locations, we need to make sure that we copy the state values at the right times and we need to make sure we are mutating the right memory locations and than adding them back and all that ourselves. In functional you'd just do: position = 10 times(minus(position, 2) plus(position, 10)) All you need to model is just the essential ordering inherent to the behavior you want. Not surprisingly, most modern programming language actually use pure functions for their math operations. That's why in Java: int position = 10; (position - 2) * (position + 10) will work, because minus, plus and multiply are implemented as pure functions, even in Java. That said, if you use the imperative ones instead it won't work: int position = 10; (position -= 2) * (position += 10) or if you went all imperative you'd have what I showed before: int position = 10; position -= 2; position += 10; position *= position; Now using math operations I think shows very clearly the accidental complexity that imperative has over functional in this case. What happens is all your other operations that are not math, but related to your business logic or program logic that you also model using the imperative style suffers from this added complexity which is removed if you move to the functional style. Now back to the article, they're expecting functional programming to somehow know the behavior they want, and magically figure out the essential complexity of it. It won't give you that, but it will get rid of a large amount of accidental complexity, and that was the point of the "No Silver Bullet" paper, that since the amount of essential complexity is fixed and inherent to your problem, only accidental complexity can be simplified when implementing said problem. Functional programming argues to dramatically simplify your accidental complexity, letting you focus all your attention on the essential parts. Having said that, there are things that tries to tame essential complexity as well and functional programming tends to mix with them very simply and easily. For example, the ideas around declarative programming, metaprogramming, interactive programming, static code analysis, and testable code all touch on the essential complexity parts of a problem. If you have more independently reusable pieces, that you can simply declare compositions of, or rules around them, it allows you to tackle some aspects of essential complexity. Metaprogramming lets you abstract things behind code-gen so you can more quickly reuse parts of each essential features that is the same. Interactive programming (like a REPL in Lisps or smalltalk systems) will let you have a quicker feedback loop to evaluate the effect of your essential problem and validate if it's correct letting you more quickly implement the essential complexity. Static code analysis can validate assertions of your essential complexity and quickly tell you if you might have made a mistake in it, it can also help you see a clearer mental map of how the essential complexity is modeled which can help you understand it better. Finally testable code, which functional code often is by virtue of its style, similarly lets you assess the validity of your essential behavior, and thus you can more quickly iterate over your essential complexity.
- chowells 5y agoThis is an odd complaint. Immutability doesn't make order of operations go away. (x+1)/2 is not the same operation as (x/2)+1. I find the tangent into effect systems at the end to be somewhat ironic, given what it follows. Effect systems also don't make order of operations not matter. The order in which effect handlers are run can change the semantics of code using them. Pairs of operations don't commute in general. There's no way around knowing which order things need to apply in. (x/2)+1 and (x+1)/2 are just different operations. Nothing saves you from needing to choose which one you mean.
- manmal 5y agoThanks for voicing this - in the system I work with (Swift Composable Architecture), concurrently dispatched effects are processed in the order in which they return the result of their work. The scheduler processes effect results on a single thread, so no real concurrency can happen there. If two supposedly concurrent effects mutate the same substate, then the outcome is (in theory) not even deterministic - the effect returning from their work first will „win“.
- didibus 5y agoExactly this ^ While effect systems are cool, they don't solve the problem described here, which is just that you need to be clear about the order of operations that are order dependent. The question would be, what programming style lets you most simply describe the operations and their needed order? Functional programming is often also talked to as a type of dataflow programming, and the two share a lot in common. A dataflow approach in my opinion is best suited here, and functional programming can easily be used in a dataflow style. The author's first example is actually pretty great, it defines a pipeline of operations, the pipeline is a very clear way to declare the order of operations. Another popular approach is instead of explicitly declaring the order using dataflow constructs like a pipeline or a DAG, that you define the dependencies on prior operations and data, and the construct infers the order from those declared dependencies.
- dgb23 5y agoI have to agree here. The author confuses what immutability is about by implicitly extending to control issues which are orthogonal to immutability. The author describes the problem very well but misuses the term "state" to describe "control". The solution to control issues is not having your code depend on order. Logic programming, SQL, schema, type declarations, regex... Many of us use these all the time but logic/declarative programming in general is not the norm in $dayJobLang.
- cloogshicer 5y agoI don't understand the negativity in some of the comments. I thought this was an excellent article. Two main takeaways for me: - While immutability can prevent some state-related bugs, it will not prevent them if the state changes are not commutative - I especially liked this sentence: > This is actually very similar to the problem with imperative languages – since everything runs sequentially, it’s hard to see what parts of the code have a true sequential dependency and which parts do not.
- elwell 5y agoHere's a little immutable + effects (Re-frame) game with some physics I'm working on: https://github.com/celwell/wordsmith https://github.com/celwell/wordsmith See the 'pipleline' of transformations applied here: https://github.com/celwell/wordsmith/blob/0dff5446278b22a5b01f5c3acaa3b6b4498c6619/src/cljs/wordsmith/events.cljs#L72-L89 https://github.com/celwell/wordsmith/blob/0dff5446278b22a5b0...
- edem 5y agoNo, you also need persistent data structures.
- mmazing 5y ago"After experiencing the kinds of bugs I described above, it became clear to me that – despite their merits – immutable data structures are not a silver bullet." There ARE NO silver bullets. So many developers want them, but they don't exist. It's a gigantic form of laziness that causes so many problems in software teams, and people drowning in it can't see it, or refuse to. Good developers that see tools in a toolbox instead of constantly searching for silver bullets are rare and should be retained at great cost.
- scaredginger 5y agoOne thing I like about this article is that it tries to evaluate how pure functional designs handle state. I've found a lot of posts give examples of programs with no state and show how elegant a functional implementation is. Most real-world programs do have a lot of state within them, so for a fair comparison, we should see how a functional design handles transitioning that state, compared to an imperative approach.
- odipar 5y agoI think immutability (or immutable state) adds one big advantage over mutable state: you have access to previous state(s) and the current state. Immutability is very convenient for checking post-conditions that compare previous states and the current state for certain properties that should hold (temporal properties for example). In turn, post-conditions guard your states to be sane.
- earthboundkid 5y agoThis was a very interesting presentation, but I think the author is too wedded to immutability to just come out and state the obvious: it’s a solution in search of a problem, and there’s not much good in using it as is. Yes, IF your problem can be decomposed into pure functional bits, that often makes a program easier to read. But if not, it’s wishful thinking to try bolting immutability onto the problem and hope that it helps. Returning state new objects is a sign that this problem doesn’t decompose the proper way to benefit from this approach! In game dev, there’s a popular pattern called “Entity Component System” which deals with these problems quite well. The components are just blobs of pure data, like the state in the article, but instead of freezing them for no real reason, you compose systems to make them interact in predictable ways. It’s much easier to reason about, which makes it the right tool for this particular job.
- earthboundkid 5y agoThe premise of immutability is that this causes bugs: doA() doB() doC() and so the solution is instead of doing: State state doA(&state) doB(&state) doC(&state) you do State state state2 = doA(state) state3 = doB(state2) state4 = doC(state3) But obviously, the only difference between 2 and 3 is that 3 is a pain in the ass to type out. All of the benefit is there in 2 because you're explicitly declaring what it is that the functions are modifying, instead of having them modify unseen globals. There's no advantage to adding a ugly calling convention on top of that.