| But this is the problem with basically every functional programming article that makes it's way to HN. As a thought experiment it makes sense but it's not transferrable to the real world. Like take the log out action as an example. Mid request we are going to have to change the state from a user logged in to a user logged out. Otherwise when we render the home screen, as an example, they'll see the logged in version instead of the anonymous user one. And realistically we want need a transition state to show a successfully logged out message since we do not want that message to show the next time they come. So the whole sequence for this simple action is: Old State -> Intermediate State -> Render Response -> New State for the next request. So we have to change state a couple times and pass that along. And then how do we deal with stuff that's actually state. Like a database or whatever. Are we really passing that as a function parameter so that 10 calls deep can use it? And what happens 6 months in when we want a caching layer? Do we go back and edit every single function to accept a Redis argument now? Realistically, no. We have some sort of config or server object that we pass along. So we have an object that contains all of our server state that gets passed to most functions. How is that better than a single global variable holding that state accessible everywhere in the application? |
> ... as a thought experiment it makes sense but it's not transferrable to the real world.
For the record, it's certainly not a thought experiment, I have been using this in the real world for a few years.
> Like take the log out action as an example...
The problem you outline with regards to intermediate UI states around the logout flow example is one of assumed responsibilities. If an application needs to show some specific transient UI when the application has moved between two states well, that's a UI concern and does not have to be modelled as part of the core application state. For example if you have a UI for an application which is required to show some jazzy animation when a user transitions from logged-in to logged-out that in no way needs to be part of the core application logic. The UI can just inspect the incoming state and conditionally when detecting a move from LoggedIn to LoggedOut trigger some UI animation / transient dialog / whatever.
One nice thought experiment / thought pump I find of value when dividing core application (~domain) and UI responsibilities is thinking about if you were to switch the UI from say a mobile framework (say Android/Compose) to a desktop terminal, what stuff stays the same and what stuff is display specific. In the above example you most likely would not want to show a jazzy animation on the terminal and therefore is display specific and should no be modelled in the core application logic. This is a nice simple thought process for dividing these responsibilities.
> And then how do we deal with stuff that's actually state. Like a database or whatever. Are we really passing that as a function parameter so that 10 calls deep can use it?
No, a database would fall under IO and would make more sense to access behind a Command interface in this pattern, like most other side-effect interactions. This fits into the "functional core imperative shell" mindset referred to in the post.
> And what happens 6 months in when we want a caching layer? Do we go back and edit every single function to accept a Redis argument now? Realistically...
I am in no way experienced with BE caching layers, my experience is mostly client side mobile applications. However, my first approach for an in-memory DB cache would be to chuck that in front of the DB access behind the same abstraction, and also this would be behind the Command interface, again like all other side-effect interactions.