17

How do I construct a system that has all of the following:

  1. Using pure functions with immutable objects.
  2. Only pass into a function data that the function it needs, no more (i.e. no big application state object)
  3. Avoid having too many arguments to functions.
  4. 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.

Background

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.

My Question:

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.

4 Answers4

4

I can't speak to C#, but in Haskell, you would end up passing the whole state around. You could do this either explicitly or with a State monad. One thing you can do to address the issue of functions receiving more information then they need is to use Has typeclasses. (If you're not familiar, Haskell typeclasses are a little like C# interfaces.) For each element E of the state, you can define a typeclass HasE that requires a function getE that returns the value of E. The State monad can then be made an instance of all these typeclasses. Then in your actual functions, instead of explicitly requiring your State monad, you require any monad that belongs to the Has typeclasses for the elements you do need; that restricts what the function can do with the monad it's using. For more info on this approach, see Michael Snoyman's post on the ReaderT design pattern.

You could probably replicate something like this in C#, depending on how you're defining the state that's being passed around. If you have something like

public class MyState
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
}

you could define interfaces IHasMyInt and IHasMyString with methods GetMyInt and GetMyString respectively. The state class then looks like:

public class MyState : IHasMyInt, IHasMyString
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
    public double MyDouble {get; set; }

    public int GetMyInt () 
    {
        return MyInt;
    }

    public string GetMyString ()
    {
        return MyString;
    }

    public double GetMyDouble ()
    {
        return MyDouble;
    }
}

then your methods could require IHasMyInt, IHasMyString, or the whole MyState as appropriate.

You can then use the where constraint on the function definition so that you can pass the state object, but it can only get to string and int, not double.

public static T DoSomething<T>(T state) where T : IHasMyString, IHasMyInt
{
    var s = state.GetMyString();
    var i = state.GetMyInt();
    return state;
}
DylanSp
  • 327
3

I'm not sure if there's a good solution. This may or not be an answer, but it's far too long for a comment. I was doing a similar thing and the following tricks have helped:

  • Split the GameState hierarchically, so you get 3-5 smaller parts instead of 15.
  • Let it implements interfaces, so your methods see only the needed parts. Don't ever cast them back as you'd lie to yourself about the real type.
  • Let also the parts implement interfaces, so you get fine control of what you pass.
  • Use parameter objects, but do it sparingly and try to turn them them into real objects with their own behavior.
  • Sometimes passing slightly more than needed is better than a lengthy parameter list.

So now I'm wondering if the problem I have is that my functions are nested too deeply.

I don't think so. Refactoring into small functions is right, but maybe you could regroup them better. Sometimes, it isn't possible, sometimes it just needs a second (or third) look at the problem.

Compare your design to the mutable one. Are there things which have got worse by the rewrite? If so, can't you make them better in the same way you did originally?

maaartinus
  • 2,713
1

I think you would do well to learn about Redux or Elm and how they handle this question.

Basically, you have one pure function that takes the entire state and the action the user performed and returns the new state.

That function then calls other pure functions, each of which handles a particular piece of the state. Depending on the action, many of these functions may do nothing but return the original state unchanged.

To learn more, Google the Elm Architecture or Redux.js.org.

Daniel T.
  • 3,053
-2

I think what you are trying to do is to use an object oriented language in a way it should not be used, as if it was a pure functional one. It's not like OO languages were all the evil. There are advanteges of either approach so that's why we can now mix OO style with functional style and have the opportunity to make some pieces of code functional whereas other remain object oriented so that we can take advantage of all the reusability, inheritance or polimophism. Luckily we are no longer bound to either approach only so why are you trying to confine yourself to one of them?

Answering your question: no, I do not weave any particular states through the application logic but use what is appropriate for the current use case and apply the available techiques in the most appropriate way.

C# is not ready (yet) to be used as functional as you'd like it to be.

t3chb0t
  • 2,602