1

What's the difference between writing OO code that depends on internal state and writing a pure function that expects an argument that is a data structure of a specific type (and thus has internal data that could be different)? So function f(x) where x is a complex object that may be { foo: 'bar', biz: 'blah' } but will have a consistent, defined structure.

Is the pure function no longer pure if the function signature is a data structure that's complex and changing the values within the data structure passed change the return of the function? Or is it still a pure function because if you pass the same data structure with the same internal values the function will return the same value?

2 Answers 2

5

A pure function is a function that:

  • for the same arguments received it always returns the same result;
  • running the function has no side effects.

So these are properties of the function. It doesn't matter if the parameter you send to the function is a complex structure or not. If the function respects the above two conditions then it's a pure function. See the linked Wikipedia page for some example of pure and impure behavior.

You asked the question in regards to OOP, so in that context most functions are impure (when bound to an object the function is now called a method) because although they can usually behave in a "pure" manner, they often don't just compute a result based on the received parameters alone, but also change the object they were called on and can access other external data from the system when doing so (like static fields of the class, or other classes).

As for advantages of using pure functions instead of methods that often are written with side effects, the biggest advantage would be that there are less hidden problems. You know exactly what happens to your data when you run a pure function because execution does not depend on any hidden state. So, your code is easier to test. If you have a bug, you simply invoke a function with known parameters and observe the result. Is the result as expected? No? Then the issue is in the function code, and not because somewhere, something external to the function was mutated and it's now involved in the computation of your function, and now you need to look for all places where that thing got mutated, and so on. It basically makes it easier to reason about the data and the behavior because you don't have them both mixed in together in non-visible ways.

There is also a matter of reusing code. In FP code reuse is done with function composition, in OOP is usually done with inheritance. Function composition with no side effects is easier to reason about than relying on (usually) deep hierarchies of tightly coupled classes, with each subclass having the potential of overriding the things inherited from its ancestors.

The topic is very large, but a blunt simplification would be that, with FP you have a more stateless, immutable approach to things, that is easier to reason about and troubleshoot, than having a stateful, mutable programming model like in OOP. A simplistic visual difference would be something like the following.

For OOP, your state grows as methods operate on the data, and you don't really know at a glance who did what, something like this: enter image description here

With FP, things are a bit more clean, something like this:

enter image description here

Of course, pure functional programming dealing with no state mutations or side effects would be mostly useless or resume itself to mathematical problems only, so there are ways to have side effects in FP, but it's usually a thing confined at the edge of your system and you have to deal with it explicitly (i.e it's not hidden).

5
  • I accept that as theory. But my question becomes, why is internal state such a concern when comparing OO & functional programming, if functional programming with complex state and OO can effectively be a question of appearance. Maybe even superficial?
    – Andrew
    Dec 22, 2020 at 11:44
  • 1
    @Andrew: so basically, you are asking what advantages does FP have over OOP when managing state? Did I understand that correctly?
    – Bogdan
    Dec 22, 2020 at 12:04
  • essentially yes, that's my question.
    – Andrew
    Dec 22, 2020 at 12:13
  • 1
    @Bogdan, it's a little misleading to say that an object member method must be impure (at least within the reasons you state). A standard OO syntax like obj.method(arg) is just syntactic sugar for obj.method(obj, arg), so provided obj is not modified by the method, and providing the contents of obj are not externally modified, then such a method call is pure since it operates purely from its arguments with no side effects. I concede many member methods in OOP do modify the object, but they do not have to, and one can draft an object which carries state yet consists only of pure methods.
    – Steve
    Dec 22, 2020 at 12:59
  • @Steve: yes, that is correct. My wording was too simplistic, I will rephrase. Methods can behave in a "pure" manner, but they often change the object, or can rely on other fields of their class (like static variables for example).
    – Bogdan
    Dec 22, 2020 at 13:11
1

Pure functions, as discussed by Bogdan, have two important features:

  • They give the same result for the same arguments. That means their return value cannot depend on global variables, external input and the like. A function of type (say) MyObject -> int can be pure as long as only the attributes of influence the return values.
  • They do not have any (observable) side effects. They cannot produce external output, modify global variables or so on. A function MyObject -> int that modifies its input argument is thus impure, because the instance of MyObject can be observed to have been changed after the function call.

So why does that matter? Well, for pure functions it doesn't matter when you call them or if you even call them at all. You can also re-use their return value.

For example, take this function:

func fib(n):
    if n <= 1:
        return 1
    return fib(n - 1) + fib(n - 2)

This function has a runtime of O(2^n), right? Well, a programming language that knows about pure functions can make it run in O(n), by memoization. fib(n) only needs to be called at most once for any n, because afterwards, it can remember the return value. Perhaps even more impressive, if you have fib(100) somewhere in your code, the compiler could replace that with a constant 573147844013817084101, requiring no computation at all!

Aside from performance benefits, it is easier for your computer to reason about your program, which helps if you want it to be provably correct. Even if proving correctness is not practical or possible, it is easier to find bugs in programs with pure functions.

But how can we mesh this with OOP? Well, that is tough, it only works if objects are immutable.

Instead of a function MyObject -> int that modifies the MyObject, you would have to write a function MyObject -> Tuple<MyObject, int> that constructs a new MyObject and leaves the original one alone. It is something to get used to, but it works. If you'd like to see how that works in practice, I recommend this collection of blog posts by James Hague.

Functional programming isn't an all-or-nothing proposition, luckily. Even C++ compilers can to a certain extent make use of the benefits I described above. Or think of Rust, which allows mutable state only if you use the mut keyword. Or you could use what you've learned about functional programming to improve the maintainability of your Java projects, by making methods depend only on the value of their arguments (including this) where possible, and keeping side-effects limited to a small number of methods.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.