7 ms·
Why does update(...) mutate the model instead of returning the next state? This would make update a function instead of a side effect mutating the model.
by kochthesecond 9y ago
Why does update(...) mutate the model instead of returning the next state? This would make update a function instead of a side effect mutating the model.
- vvanders 9y agoProbably for performance reasons, allocating a whole new state each frame is pretty expensive.
- kochthesecond 9y agoI would argue that it is obviously worth it for the simplicity of being explicit what is the next state. This kind of performance optimization, especially outside of game engines seem unnecessary.
- cube2222 9y agoThis is Rust. Not exactly a language that makes compromises on performance for the sake of simplicity.
- vvanders 9y agoEach cycle you waste on mobile is milliwatts you're burning from the battery. Even if the user doesn't perceive it their battery will appreciate it.
- kochthesecond 9y agoYou save a single allocation for every state change in your app? Never mind the actual render cycle? And lose a simple reference check to decide if you can short-circuit a part of your app?
- algesten 9y agoit's not any more expensive than allocating a new object in Javascript. possibly less. if you want immutable models, allocation will most likely happen on mutation.
- jcelerier 9y ago> it's not any more expensive than allocating a new object in Javascript. so you mean bloody expensive ? seriously, if everybody thinks like you no surprise the current web crawls on beastly computers.
- whateveracct 9y agoIdk about JS's memory model, but you can allocate the equivalent of a JS object in Java and Haskell very, very cheaply. I really don't think allocating a single JS object is expensive...updates to large immutable data structures should just require a few allocations (aka a handful of pointer bumps). Sure, it's technically more expensive than an in-place update to an equivalent large mutable data structure. But it's also not a fair comparison given one gives you way stronger guarantees about its behavior.
- vvanders 9y agoExcept in those languages it can be just as brutally painful to allocate. Start modifying strings in the render loop on Android and see how quickly you get destroyed by constant GC pauses. The only way to address it is with extensive pooling and heuristics. Or you can just mutate. Really it's no wonder that the web is so slow if the common conception is that allocating is no big deal. If you really want to do performance right allocations should be at the top of your list right next to how cache friendly your data structures are.
- imtringued 9y agoBecause in reality it is no big deal. Modern GCs are incredibly efficient. When a GC allocates memory all it does is check if there is enough memory in the "young generation" if yes it will increment a pointer and then it will just return the last position in the young generation. If there is not enough memory in the young generation the GC will start traversing the GC root nodes on the stack and only the "live" memory has to be traversed and copied over to the old generation. In other words in the majority of cases allocation memory costs almost as much as mutating memory.
- whateveracct 9y agoIncremental updates to large, immutable data structures do not require allocating a whole new state per update (despite maintaining immutability!) Mutation is still frequently more efficient though, at the cost of not being able to reason immutably about your program. But if you're doing something that requires multiple versions of your state (e.g. history, undo/backtracking), the immutable version is going to be more efficient and definitely easier to write efficiently in the first place.
- vvanders 9y agoI'll give you that it'll be easier to write but once you start working with value types(like Rust has proper support for) then you don't get the nice efficiencies that come when everything in the world is a reference.
- imtringued 9y agoMost efficiency gains of imperative programming languages like C or C++ come from the compact and continguous memory layout and usage of the stack which is usually in the L1 cache. In theory there would be no difference between mutating an existing memory area and allocating new memory to write to it. The number of bytes written to RAM are the same.
- lossolo 9y ago> In theory there would be no difference between mutating an existing memory area and allocating new memory to write to it. The number of bytes written to RAM are the same. It's not the same, you need to allocate new memory, if it's on the heap then (depending on your allocator/OS etc) you will be making a syscall, which means context switch, cache eviction etc.
- vvanders 9y agoThis is where understanding the difference between theory and practice is important, by its nature anything in the heap is not going to be in L1 cache. That means a difference of ~300 cycles before you can even start working with the cache line you pull from main memory.
- Flow 9y agoBut the state is likely a tree structure and there will be lots of sharing if coded right. I think frameworks like React should explicitly demand that state and props will be referentially compared to optimize the rendering.
- skariel123 9y agomutating in Rust is not like mutating in JS, Python, Java C# or many other languages. Rust tracks ownership, you always know who might mutate the object and when. It is a very unique experience programming Rust
- kochthesecond 9y agoThis might be more sane in Rust, I am not familiar with Rust. I have used a lot of redux in js and written a few things in Elm, and the immutible property of application state is very central in both, and an important part of being able to reason with the ui, as well as events happening concurrently.
- alkonaut 9y agoImmutability and ownership arent unrelated. They solve the same problem. The problem with shared mutable state might appear to be then “mutable” but it’s the combination of shared+mutable. Ownership removes one (if it’s mutable it’s not shared). Immutability removes the other. The outcome is the same: you can reason about your code, including concurrent code. The price of ownership is some extra mental overhead. The price of immutability is some extra copying and cumbersome updates. In JS/Elm you have no choice but to manage shared mutable state using pure functions and immutable data. In rust you can use either model.
- szemet 9y agoIn rust you can use either model. Not really. Of course you can copy immutable data upon update, but persistent data structures (large shared state between data versions)[1] are hard to write without GC... [1] https://en.m.wikipedia.org/wiki/Persistent_data_structure https://en.m.wikipedia.org/wiki/Persistent_data_structure
- littlestymaar 9y agoIf you don't have cycles, ref counting can work (it's a kind of GC after all), but it indeed comes with a big performance overhead compared to a modern GC.
- rbalicki 9y agoReturning a nextState in Redux/JS land is a workaround. Basically, one wants to avoid the situation where the state is mutated, but the framework does not know. This happens because there are multiple references to the state, and because, for performance reason, the framework is comparing the states based on referential (===) equality. Thus the view (in whole or in part) and the model can go out of sync. Always returning a new state object is an expensive way of ensuring this, but on the human time scales we're talking about, an insignificant cost. On the other hand, in Rust, we have the compiler on our side. If one grabs a mutable reference to the state (&mut Model in the update function in https://github.com/DenisKolodin/yew/blob/master/examples/counter/src/main.rs https://github.com/DenisKolodin/yew/blob/master/examples/cou...), then the Rust compiler ensures that there are no other references to that Model. Thus, when the framework does `nextState = mutate(¤tState)`, one is guaranteed that there are no existing references to `¤tState`, and thus it is impossible for a shadow update to occur. IMO, this is better not primarily for performance reasons, but because it is conceptually easier to mutate a state object instead of using reducers. (I haven't looked into this framework much, so I don't presume to speak about it's internal implementation, so this may be off in some framework-specific details.)
- mlevental 9y agoin a nutshell this the counterargument to functional purity: side effects can be done correctly/safely when you have/build the right tools. i'm inclined to agree with it.
- staticassertion 9y agoIt's not really a counterargument so much as another approach. Immutability and purity are still powerful concepts, but you don't need to use them for every single situation in rust.
- andrewflnr 9y agoMutability is still harder to reason about in many/most cases. I thought that was always the main driver behind pure FP.
- 9y ago