DEV Community

Cover image for Pure Critique of Pure Functions in Functional Programming

Pure Critique of Pure Functions in Functional Programming

Yeom suyun on September 05, 2023

TL;DR A few days ago, I came across an article with an intriguing title on the internet. "Clean" Code, Horrible Performance Wow, that ti...
Collapse
 
lionelrowe profile image
lionel-rowe

Your "FP" example:

function increment(num) {
  return num + 1
}
const counter = {
  _count: 0,
  get_count() {
    return this._count
  },
  set_count(count) {
    this._count = count
  }
}
button.onclick = pipe(
  counter.get_count,
  increment,
  counter.set_count
)
Enter fullscreen mode Exit fullscreen mode

...isn't remotely close to FP. Passing impure methods of a mutable object to a higher-order function named pipe doesn't magically make them pure. The only pure function here is increment.

Collapse
 
artxe2 profile image
Yeom suyun

Yes, that is a correct observation.
Unfortunately, the use of the this keyword in the getter and setter in the example is not pure in any way.
I think it is an exception that the beginning and end of the pipeline violate FP even when using functional programming libraries such as RX.js.
The example description states that the example does not meet FP for several reasons, but the related content is definitely poor.

Collapse
 
skyjur profile image
Ski • Edited
  1. Why do you say that it needs to copy data 5 times (or n times for n number of functions in pipeline)? When working with larger objects I would expect a tree/like structure where functions apply modifications with minimal data being copied.

  2. Why in your counter example you're using array, and why count[0] += count[1], and why it's initialized with [0, 2] ? Seems like every case of count click will increment by 2. But it's just such an akward way to write this code. Why array in first place and not just one integer value?

  3. Regarding react example my personal opinion is just that we all should simply agree that React hooks in fact is not functional programming. It's actually a lot more like oop than it is functional programming: as it allows declaring and managing encapsulated state inside your components - much same way how we typically do in object instances in oop - think of component as instance and think of useState() as private variables. In fact such components used to be written in class-based manner but React team for whatever reason decided to confuse the hell out of developers and discard 'class' syntax in favor of 'function' syntax even though it's still OOP. Don't get tricked by syntax that suggest that it's "functional" component. If it was functional component it would not have effects and state as functional code cannot have state and effects if they do it's not functional code anymore.

Collapse
 
artxe2 profile image
Yeom suyun
  1. Yes, your opinion is entirely correct.
    In my article, I provided an example of minimizing the copy of values by using deep_copy instead of pure functions.
    The reason why the data needs to be copied 5 times is because pure functions in a pure function pipeline cannot modify the input.

  2. This is artificial code to demonstrate how React enforces immutability.

  3. I'm not sure if React's functional components are closer to OOP, but I completely agree that they are not FP.
    Functional components explicitly cause side effects, which is a very strange way to do things.
    As I said in number 2, the React example was used to argue that immutability is not a condition for clean code.

Collapse
 
skyjur profile image
Ski • Edited

Just to expand a little more on #1. I guess you're probably saying same thing but I think something still needs to be mentioned here on case of copying and copy performance.

I don't think the 'deep_copy' that you added is at all necessary.

My point is basically that, even though input is immutable, function can return a reference to original input without full copy.

For instance we have object

{
      a: {...},
      b: {...},
      c: {...}
}
Enter fullscreen mode Exit fullscreen mode

let's say we want to modify a with new attribute obj.a.name = 'new name', we can do that without copying most of data

{
    a: {...input.a, name: 'new name'},
    b: input.b,
    c: input.c
}
Enter fullscreen mode Exit fullscreen mode

We do have some copying happening, the root object is changed, and the 'a' object is copied, but content of 'b' and 'c' doesn't need to be copied, neither any inner values of a.

So it can be argued that in many cases copying can be done in lightweight shallow manner it wouldn't affect performance.

That said in many more demanding situations this still cannot achieve same performance characteristics as mutable structures. Sometimes mutable structures will simply have significant performance benefit. One reason is just the fact that when we're doing many shallow copies, objects get fragmented in memory. CPUs nowdays are designed to fetch memory in blocks not one byte by one byte and thus if we have high fragmentation we lose this benefit as well as benefit of CPU cache. But yet another reason in garbage collected languages like javascript - this approach is producing more garbage and thus is making garbage collector work more heavily which then impacts performance.

If we could safely update objects in place whilst still having benefits of functional programming that would be ideal and I think it's possible - but not many languages are supporting such concept. This could be relatively easily done by a keyword that would remove

function increment(consumable x) {
    x.count += 1
    return x
} 


function useIncrement() {
    let x = {count: 0}

    let newX = increment(x) //ok 

    x.count // error, x cannot be referenced after it was "consumed" by "increment"

    newX.count // ok
}
Enter fullscreen mode Exit fullscreen mode

such syntax would keep all functional benefits whilst still enabling working with mutable structures. But of course it is quite challenging to implement such concept as very big difficulties begin with references to deep values.

Thread Thread
 
artxe2 profile image
Yeom suyun • Edited

Sure, I agree with you.
In many cases, pure functions may not need deep copies, and deep_copy can be optionally used.
(The example using deep_copy assumes that the pure function constraint does not apply)

The last example looks very similar to the concept in Rust. It would be interesting if this approach was supported at the IDE level, such as JSDoc or ESLint.

Collapse
 
aregtech profile image
Artak Avetyan

Clean code has nothing to do with performance. Performance is optimization. Clean code is about readability and usability. If big projects don't follow clean code, they endup with chaos, where each new feature and extension is a pain.

Collapse
 
artxe2 profile image
Yeom suyun

My post may have been poorly written and failed to convey my meaning. I did not intend to criticize Clean Code.

My point is that it is more aligned with human thinking to limit the scope of side effects to a certain scope, such as objects or files, rather than trying to completely eliminate side effects. Therefore, FP's pure functions are not only slower, as everyone acknowledges, but also do not seem to have any maintenance advantages over OOP or others.

Collapse
 
aregtech profile image
Artak Avetyan

My comment was mainly referred to the article, because the author was criticizing the clean code and bringing examples to prove that there are codes with better performance.

About the FP, it has advantages. One of them, with FP you make easier modular software. If the concept of OOP is around object, data and its states, in FP the concept of states and orders is discarded. Just recently I wrote a small text about OOP and FP essentials. I think these 2 concepts are not competing, but extend each other.

As a practical example, I'm working on an open source framework, which is based on ORPC (Object RPC). On one hand the RPC can be considered as FP, on the other hand, it is object based, meaning it may have Data (Attributes) and Types. In short, the framework eases multithreading and multiprocessing programming by treating remote objects like locals, and it makes the location of objects transparent. Once you define the interface of your object (Service Interface), you can make multiple implementations of the same interface (FP concept) and even instantiate same implemented object multiple times, but you access them by their unique names (OOP concept) called Role Names. Meaning the consumer of the remote object should know both -- the Service Interface and the Role Name. In my opinion, here the FP and OOP concepts are quite well intertwined.

Thread Thread
 
artxe2 profile image
Yeom suyun • Edited

I have read the article and GitHub README.md you provided, but I apologize that I did not understand it properly due to my limited knowledge.
It seems that you have introduced the concepts of FP to the advantages of OOP, such as forcing the implementation of certain functions according to objects or reusing functions such as lifecycle, to isolate from the environment and prevent side effects.
I think the concepts of FP and OOP or other methodologies can certainly be integrated.
In the end of my article, I also limited the target of criticism to extreme functional programming.
However, in extreme functional programming, functions cannot read any data other than the input, and cannot write any data other than the output.
Everything must be implemented declaratively by combining these pure functions.
And I think this is probably an unnecessary constraint for you as well.

Thread Thread
 
aregtech profile image
Artak Avetyan

Yes, agree. That's why I think it is suitable for modular applications, where you are not interested what is inside modules, you are focusing on input and outputs of the modules. Also with FP it is easier to scale the modules. This is where I see the biggest advantage of the FP, rather than in performance.

Collapse
 
booniepepper profile image
J.R. Hill

What did I even just read? Bad code is bad regardless of whether it's OO or FP or something else.

Collapse
 
artxe2 profile image
Yeom suyun

Maybe this is the key.
The condition of good code is irrelevant to whether it is written in FP.

Collapse
 
bwca profile image
Volodymyr Yepishev

The zealots will be upon you in no time 😂