Domanda

What are the reasons, technical or otherwise, that pure functions (functions which do not rely on external state and have no side effects) should not always be public and static?

When a static and pure function is very general, such as Math.floor() or something, then it makes total sense to have it be public and static. Why shouldn't we have global access to basic math functions? But when you do the same with functions specific to the application domain, specific even to JUST the class they are implemented in, then I begin to feel like I'm doing something wrong.

For example, let's say a we have a class User, and it has a super specific attribute called happiness, which can be changed by eating food. Generally I would have a public function acting as an interface, getting the outside information, then call a protected helper function which is purely implemented, passing in any stateful data as arguments. The implementation might look like this:

class User {
    private FoodPreference foodPreference; 
    private float age; 
    // ... 
    // constructors initializing the users food preferences and
    // changing the Users preferences as they get older
    // ...

    // Impure interface relying on users state to get their preferences
    public void EatFood(Food food) {
        this.happiness = this.ComputeHappiness(this.foodPreference, food); 
    }

    // Pure, does not rely on Users state to compute this info, it's passed in as an argument.  
    protected Happiness ComputeHappiness(FoodPreference preference, Food food) {
      Happiness newHappiness = /* some user-specific logic computing their happiness due to eating food */
      return newHappiness; 
    }

}

If ComputeHappiness() was public and static, would that be bad? My knee-jerk reaction is "Yes it would be", I think because it seems that the usage of the keyword static in OOP is something generally avoided since it is so easy to shoot yourself in the foot.

I can see a lot of benefits to having pure functions be static and public, even if they are not very general:

  • Pure functions are easier to test. Static functions are easy to access. Your unit tests will need to do less setup to produce the means to test the function. Now you just need the data, which usually isn't the hard part of testing functions.
  • You public functions can be interfaces which access the stateful data (from the object, database, or any other source) and leave the business logic to their purely implemented helper functions.
  • Pure functions can be reused much more easily. Perhaps other parts of the application would like to ComputeHappiness()!

What are the reasons this is a terrible idea? It certainly seems to violate the principle of least astonishment.

È stato utile?

Soluzione

It's a question of information hiding and data abstraction. If you make your operations (even if they are pure and referentially transparent) separate from the object, then the object must make its representation public to the operations. Since hiding the representation is an important (or, according to some definitions even the only) characteristic of OOP, your object then stops being an object and becomes basically a struct.

Whereas with methods that belong to the object, the method has privileged access to the representation of the object, and can thus make use of the representation in its calculation, without the object leaking its internals to other parts of the system.

See On Understanding Data Abstraction, Revisited by William R. Cook for the distinction between Data Abstraction with Abstract Data Types and Object-Oriented Data Abstraction. The paper also contains an example of object-oriented data abstraction in Java that uses only pure, referentially transparent methods. In fact, an important point the author makes in that paper is that OO and mutability are orthogonal.

Altri suggerimenti

Every public function is part of component's interface. Interfaces are restricted in their changes, they need to be backward compatible most of the time, and when they aren't, their clients should adapt to changes. Interfaces therefore tend to be as narrow, as effectively possible. Leaking implementation detail into interface destroys modularity, and introduces additional maintenance cost.

What are the reasons, technical or otherwise, that pure functions (functions which do not rely on external state and have no side effects) should not always be public and static?

Pure functions can be public and static, so if it suits your needs, you can make all your pure functions public and static. Nobody said that they should not be public and static. But when you mention "always be public and static" you appear to be asking why isn't there a rule which forces everyone to make all their pure functions public and static. Well, there are many many reasons for that, but I doubt that this is what you meant to ask. So, you might want to re-word your question a bit, because as it stands, it does not make much sense.

Why shouldn't we have global access to basic math functions?

Various languages give you the ability to have global access to public static functions. For example, in java, you can

import static Math.floor;

and then you can use floor( x ) instead of Math.floor( x ). If you are looking for a way to use floor( x ) without even having to explicitly statically import it, that would be an extremely bad idea because it would mean that one module may reserve the global function name floor( double x ) and then no other module may declare the same global function.

What are the reasons this is a terrible idea?

I am afraid you failed to explain, in an unambiguous and concise way, precisely what terrible idea you are talking about. Perhaps if you reword your answer, we might be able to give you some better insights.

You seem to be suffering from some confusion about how either pure functions or OOP work.

A pure function is one that relies on nothing to produce its result except the values passed to it. A (normal, non-static) method in OOP takes the object it's associated with as an implicit argument. Therefore, it's completely possible to have an object method as a pure function. Use them when they make sense, and use static methods when they make sense, and you should be fine.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top