How do I construct a system that has all of the following:
- Using pure functions with immutable objects.
- Only pass into a function data that the function it needs, no more (i.e. no big application state object)
- Avoid having too many arguments to functions.
- Avoid having to construct new objects just for the purpose of packing and unpacking parameters to functions, simply to avoid too many parameters being passed to functions. If I'm going to pack multiple items to a function as a single object, I want that object to be the owner of that data, not something constructed temporarily
It seems to me that the State monad breaks rule #2, although it's not obvious because it's weaved in through the monad.
I have a feeling i need to use Lenses somehow, but very little is written about it for non-Functional languages.
As an exercise, I'm converting one of my existing applications from an object-oriented style to a functional style. The first thing I'm trying to do is to make as much of the inner-core of the application as possible.
One thing I've heard is that how to manage "State" in a purely-functional language, and this is what I believe is done by State monads, is that logically, you call a pure function, "passing in the state of the world as it is", then when the function returns, it returns to you the state of the world as it has changed.
To illustrate, the way you can do a "hello world" in a purely functional way is kinda like, you pass in your program that state of the screen, and receive back the state of the screen with "hello world" printed on it. So technically, you're making a call to a pure function, and there are no side-effects.
Based on that, I went through my application, and: 1. First put all my application state into a single global object (GameState) 2. Second, I made GameState immutable. You can't change it. If you need a change, you have to construct a new one. I did this by adding a copy-constructor, that optionally takes one or more fields that changed. 3. To each application, I pass in the GameState as a parameter. Within the function, after it's doing what it's gonna do, it creates a new GameState and returns it.
How I have a pure functional core, and a loop on the outside that feeds that GameState into the main workflow loop of the application.
Now, my problem is that, the GameState has about 15 different immutable objects. Many of the functions at the lowest level only operate on few of those objects, such as keeping score. So, let's say I have a function that calculates the score. Today, the GameState is passed to this function, which modifies the score by creating new GameState with a new score.
Something about that seems wrong. The function doesn't need the entirety of GameState. It just needs the Score object. So I updated it to pass in the Score, and return the Score only.
That seemed to make sense, so I went further with other functions. Some functions would require me to pass in 2, 3 or 4 parameters from the GameState, but as I used the pattern all the way the outer core of the application, I'm passing in more and more of the application state. Like, at the top of the workflow loop, I would call a method, that would call method that would call a method, etc., all the way down to where the score is calculated. That means the current score is passed along through all those layers just because a function at the very bottom is going to calculate the score.
So now I have functions with sometimes dozens of parameters. I could put those parameters into an object to lower the number of parameters, but then I would like that class to be the master location of the state application state, rather than an object that's simply constructed at the time of the call simply to avoid passing in multiple parameters, and then unpack them.
So now I'm wondering if the problem I have is that my functions are nested too deeply. This is the result of wanting to have small functions, so I refactor when a function gets to big, and split it into multiple smaller functions. But doing that produces a deeper hierarchy, and anything passed into the inner functions need to be passed in to the outer function even if the outer function isn't operating on those objects directly.
It seemed like simply passing in the GameState along the way avoided this problem. But I am back to the original problem of passing in more information to a function than the function needs.