Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Functional Programming Is Just a Safety Restriction on Object Orientation

DZone's Guide to

Functional Programming Is Just a Safety Restriction on Object Orientation

Learn more about the difference in limitations between functional programming and object orientation.

· Java Zone ·
Free Resource

Get the Edge with a Professional Java IDE. 30-day free trial.

I find that functional languages are an admiration of it's own idealized self. Their pursuit of mathematical (lambda calculus) purity forgets that systems run in the real world and not in theory. Yes, for academia and advancing compilers, they are great where idealized theory is the endeavour. However, their idealism is neglectful of the limitations and realities of computing for commercial development. Why be shamed for wanting to allow side effects and mutating objects? Especially when functional programming just further restricts object orientation.

Limitations

To illustrate my point of limitations within computers, let's use the typical functional programming example of calculating a particular value for the Fibonacci sequence.

For imperative (object-oriented) programming, the code would look as follows:

public BigInteger imperative_OO_Fibonacci(int term) {
  BigInteger nMinus1 = new BigInteger("0");
  if (term == 0) return nMinus1;
  BigInteger value = new BigInteger("1");
  for (int i = 1; i < term; i++) {
    BigInteger temp = value;
    value = value.add(nMinus1);
    nMinus1 = temp;
  }
  return value;
}


While for functional programming:

public BigInteger functionalFibonacci(int term, BigInteger nMinus1, BigInteger nMinus2) {
  if (term == 0) return nMinus2;
  if (term == 1) return nMinus1;
  return functionalFibonacci(term - 1, nMinus1.add(nMinus2), nMinus1);
}


Now, the functional code is certainly a lot more succinct. The imperative code takes a little bit of reading to follow and has more variables to consider. Plus, the iterative code is doing that mutating of value that functional programming is heavily against.

Functional programming seems to be the winner. It is easier to read and with less code much less likely to incur coding errors. Yep, let's get rid of all our imperative code and start doing things functionally! OK, this is a small test case to base such a decision on. However, the Internet is full of examples touting this ease of functional programming (pushed by frameworks, such as Spring Reactive).

However, I have to ask whether the functional code is really better than the imperative code? Do they both produce the correct answer all of the time?

Let's do some testing:

term Imperative Functional

0

0

0

1

1

1

2

1

1

3

2

2

Yep, all's looking good so far.

Now, let's test some boundary conditions. Well, both don't cater to negative terms. So, let's try some larger terms:

term Imperative Functional

10,000

3364... (full numbers removed for brevity)

3364...

15,000

2531...

StackOverFlowError

1,000,000

(wait a while then) 1953...

StackOverFlowError

Oh, that lovely readable functional code can't compute any terms larger than around 10,000 (for a default Java8 JVM). Yes, some functional languages have things like tail call optimizations to avoid this problem. However, this highlights issues with idealized attributes of functional programming, such as immutability.

The functional code actually has two problems that the imperative code avoids.

Problem One: Recursion Can Quickly Fill the Thread Stack

The first problem is that every function call is loaded onto the thread stack. As more and more functions get recursively called into, the larger the thread stack grows. As thread stacks must be limited on memory, at some point, the thread stack runs out of memory. At this point, you get a StackOverFlowError (not the answer you might be expecting in an idealized view of the world).

The functional code is all recursive. The higher the term, the more memory is required on the thread stack to handle the depth of recursive function calls.

The imperative code avoids this problem by just running afor loop. OK, the for loop counter is mutated and the variables of the method keep changing, but the thread stack stays constant in size throughout the execution.

Ok, let's increase the thread stack size to some very large value. Well, you walk into the next problem with recursive functional code.

Problem Two: Recursion Does Not Release References

At some point, both executions will run out of memory. The heap space to hold the BigInteger objects will be exceeded and an OutOfMemoryError will be thrown.

The imperative code changes the variables of the method to refer to the newBigInteger objects created. This allows the old, no longer referenced, BigInteger objects to be garbage collected.

The functional code, however, keeps loading the variable references onto the thread stack as it recursively executes. This means that the functional code keeps reference to every BigInteger it creates until the answer is reached. The functional code disallows garbage collection of older BigInteger objects and subsequently fills memory much faster.

Again, the imperative code will be able to produce the correct results for much larger terms.

Mutation Is Necessary

OK, so who ever asks for the millionth term of the Fibonacci sequence?

Most of the time, we are generating web pages, REST payloads, and other much smaller calculations.

Let the hardware advances deal with the limitation problem. Computers are getting more advanced and have significantly more memory available. We can just push the problem to hardware like we always do when needing to scale. However, with the advances in computer memory and increased CPU performance, some things are still finite.

The pixels on the screen you are reading this article with are finite. Functional programming would dictate that I get a new set of immutable pixels for every display update. Imagine that — immutable screens. Every time I want to display something new, I need a new monitor. At 60Hz, my desk will fill up pretty qui......ckly (sorry ju..st cle..arin..g my de..sk to cont..inue writ..ing th..is). Yep, that immutable idealization works well in unbounded theoretical mathematics but not in the bounded computer.

Oh, excuse me. We don't have immutable screens; we have immutable framebuffers. Framebuffers are buffers in RAM containing bitmaps for video drivers to use in displaying on screen. CPUs write to them what is to be displayed and video drivers read from them to display to the screen.

Well, actually, no. We don't have immutable framebuffers. The expense of garbage collecting all these created framebuffers would be excessive. Typically, we create one (possibly two) framebuffers and mutate them.

Functional programming requires mutation if it wants to display anything to screen.

Well, it's server side coding that functional programming excels.

This buffer problem is also present on the network cards. There is only so much buffer space available, and it requires being mutated to be get out onto the wire for that REST response.

In essence, functional programming requires mutation to do anything more than create memory in a computer. If functional programming wants to be of any value by displaying to screen or interacting with other systems, it requires mutation. OK, the coding by the developer does not see this. However, the mutation is there within computers limiting the purity of functional concepts.

Actually, this is where the relationship between imperative and functional programming starts to become evident.

Memory Restrictions

To see the relationship between imperative and functional programming, we need to look at memory structures used within computers and programming languages. The following table goes from least restrictive to most restrictive. It also demonstrates how each level further restricts the previous level.

Memory Level

Restriction on previous level

Description

Hardware

The physical RAM, CPU caches, swap space, etc.

Process Space

Addressable finite amount of memory for process

The operating system provides memory address abstractions that allow a process to reference its available memory.

Buffers

Process memory is requested from the operating system as a buffer. This allows direct bit manipulation of memory.

The addressable space is much larger than the available RAM/swap space/etc. of the computer. Hence, the operating system allocates the memory only on demand/request. Typical programming at this level is for HTTP network communication, screen displays, and other low level functionality.

Structs

A Struct can be thought of as a buffer that organizes the bits into higher-level fields (e.g. integers, bytes, longs, floats, chars, address references, etc.)

Structs enable imperative programming to pass by reference.

Classes

Makes the Struct fields private by default, with methods controlling accessing/mutating the fields

Classes are the foundation of object-oriented programming.

Immutable Objects

Disallows mutating the fields.

Immutable objects are the foundation of pure functional programming.


So, from a memory point of view, functional programming can be considered more restrictive programming than imperative/object-oriented programming (and buffer, operating-system programming before that).

This restriction of what the developer can do allows more prediction by the compiler to optimize code. As objects can't be mutated, then functions become more predictable. For the same input objects, the compiler could cache (memoization) — the result to avoid expensive recalculations for a performance boost. Order of calculating the functions could be changed (currying) to better optimize execution and possibly avoid executing functions (lazy evaluation) — plus a host of other compiler-focused optimizations. I would expect that most developers want to avoid these topics at 2 am in the morning to meet that deadline or get that production bug fixed.

Actually, we can possibly consider that computing is finally providing data structures to support the lambda calculus theory proven back in the 1930's (yes, before computers even really existed). Mathematics is forever generating copies. It is very transformative by taking a data set and producing a new data set. By disallowing mutating objects, computing follows mathematics by always having to create new data sets (immutable objects).

However, just because your functional language models the theory does not mean it can do this to the exclusion of imperative programming. There are times when developers need the flexibility to code at lower memory restriction layers. Some problems, for just performance reasons, even require you to code at the buffer level (just see TechEmpower Benchmarks regarding some of the fastest web servers). Yes, the purity is useful for cleaner, more abstract application code, but it should not be your only proverbial tool in the language's belt.

And for those thinking that functional programming lets you compose functions together, well, Java, with lambdas, identified that first class functions can be modelled as single method objects. Yes, you can put a lot of sugar syntax and compiler support on top of this. However, the real difference between object orientation and functional programming is restrictions on memory.

Summary

Yes, used appropriately, functional programming can solve problems easier. It certainly has helped academic efforts and improved compilers. But it is not a debate of better or worse. Functional programming can just be considered a modelling restriction that allows the compiler/frameworks to make more assumptions about your code. In effect, it actually makes the compilers/frameworks easier to write at the expense of restricting developers.

However, why let compilers and functional frameworks (such as Spring Reactive) be afforded these restrictions of what you the developer can do?

Stay tuned for our next article where we will look at functional frameworks and introduce dependency contexts to avoid these restrictions.

Get the Java IDE that understands code & makes developing enjoyable. Level up your code with IntelliJ IDEA. Download the free trial.

Topics:
inversion of control ,dependency injection ,thread pools ,function composition ,functional programming ,object orientation ,java

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}