Thanks for writing the article @Yogthos ! It’s well written and deserves a few more read throughs from me ![]()
I had to work on a rather icky complicated GUI a couple years ago (with lots and lots of statefulness) and I found one thing that was a life saver was memoization (and avoiding “map managment”). It strangely seems to never come up in these conversations, I just wanted to know your thoughts on it
My own thoughts on this are a bit half baked, but just working off your example I’ll try to illustrate what I mean.
So in your example, instead of having, users, funds, emails… etc., your state would remain simply the list of transactions/events. These are much harder to mess up. You then created memoized functions/interfaces that would compute things like a list of user, the amount of funds a user has, the email of a user… etc.
All state-change then boils down to appending a transaction/event - and that’s it! As long as you don’t push a broken transaction then you should be fine. (and if you do, it’s easy to catch)
This has two primary benefits. First is your code becomes even more decoupled and stateless and it’s harder to end up with a broken state. Second, is that it becomes significantly easier to refactor as things get centralized in the memoized state-getter functions. So the system end up scaling much better.
So for instance, if say you have a new requirement where you want to track how many transaction each user has done. If you have a managed state, then you need to find each location you’ve done a transaction and add something to increment some counter (and introduce a new thing in your map that needs to be managed). While in a memoized derived state you’d just introduce a new memoized function that computed that for you directly from the transaction record.
You need to be a bit careful with the memoization cache - but I don’t think this is an intractable problem. @vlaaad has a nice state management system in CLJFX where these memoized functions can call other memoized functions and cleverly reuse results without recomputing things.
Anyways, I was wondering if you’ve looked into this side of things. I thing for large applications where there is tons of state to maintain, this approach reduces the surface area of potential issues. It’s not very black/white and you do need to settle on what is the exact “state” you’re tracking. Like in a GUI you might not want to go crazy and keep a list of all the UI interaction that are then virtually replayed to generate the current state… but you try to boil the state down to just what’s necessary, preferably in a way where you can’t make it invalid - and then have the state be interfaced through what is essentially a memoized interface. Decoupling the internal representation from the state interface.