Pure functional vs tell, don’t ask

design-patternspure-function

"The ideal number of arguments for a function is zero" is plain wrong. The ideal number of arguments is exactly the number needed to enable your function to be side-effect free. Less than that and you needlessly cause your functions to be impure thus forcing you to steer away from the pit of success and climb the gradient of pain. Sometimes "Uncle Bob" is spot on with his advice. Sometimes he is spectacularly wrong. His zero arguments advice is an example of the latter

(Source: comment by @David Arno under another question on this site)

The comment has gained a spectacular amount of 133 upvotes, which is why I'd like to pay some closer attention to its merit.

As far as I'm aware, there are two separate ways in programming: pure functional programming (what this comment is encouraging) and tell, don't ask (which from time to time is being recommended on this website as well). AFAIK these two principles are fundamentally incompatible, close to being opposites of each other: pure functional can be summarized as "only return values, have no side effects" while tell, don't ask can be summarized as "don't return anything, only have side effects". Also, I'm kind of perplexed because I thought that tell, don't ask was considered the core of OO paradigm while pure funcitons were considered the core of functional paradigm – now I see pure functions recommended in OO!

I suppose developers should likely chose one of these paradigms and stick to it? Well I must admit I could never bring myself to follow either. Oftentimes it seems convenient for me to return a value and I can't really see how can I achieve what I want to achieve only with side effects. Oftentimes it seems convenient for me to have side effects and I can't really see how can I achieve what I want to achieve only by returning values. Also, oftentimes (I guess this is horrible) I have methods that do both.

However, from these 133 upvotes I'm reasoning that currently pure functional programming is "winning" as it becomes a consensus that it is superior to tell, don't ask. Is this correct?

Therefore, on the example of this antipattern-ridden game I'm trying to make: If I wanted to bring it to conformance with pure functional paradigm – HOW?!

It seems reasonable to me to have a battle state. Since this is a turn based game, I keep battle states in a dictionary (multiplayer – there may be many battles played by many players at the same time). Whenever a player makes their turn, I call an appropriate method on the battle state which (a) modifies the state accordingly and (b) returns updates to the players, which get serialized into JSON and basically just tell them what has just happened on the board. This, I suppose, is in a flagrant violation of BOTH principles and at the same time.

OK – I could make a method RETURN a battle state instead of modifying it in place if I really wanted to. But! Will I then have to copy everything in the battle state needlessly just to return a wholly new state instead of modifying it in place?

Now perhaps if the move is an attack I could just return a characters updated HP? Problem is, it's not that simple: game rules, a move can and often will have many more effects than just removing a portion of a player's HP. For example, it may increase the distance between characters, apply special effects, etc etc.

It seems so much simpler for me to just modify the state in place and return updates…

But how would an experienced engineer tackle this?

Best Answer

Both Uncle Bob and David Arno (the author of the quote you had) have important lessons we can glean from what they wrote. I think it's worth learning the lesson and then extrapolating what that really means for you and your project.

First: Uncle Bob's Lesson

Uncle Bob is making the point that the more arguments you have in your function/method the more developers who use it have to understand. That cognitive load doesn't come for free, and if you aren't consistent with the order of the arguments, etc. the cognitive load only increases.

That is a fact of being human. I think the key mistake in Uncle Bob's Clean Code book is the statement "The ideal number of arguments for a function is zero". Minimalism is great until it isn't. Just like you never reach your limits in Calculus, you'll never reach "ideal" code--nor should you.

As Albert Einstein said, “Everything should be as simple as it can be, but not simpler”.

Second: David Arno's Lesson

The way of developing David Arno described is more functional style development than object oriented. However, functional code scales way better than traditional object oriented programming. Why? Because of locking. Any time state is mutable in an object, you run the risk of race conditions or locking contention.

Having written highly concurrent systems used in simulations and other server side applications, the functional model works wonders. I can attest to the improvements the approach has made. However, it is a very different style of development, with different requirements and idioms.

Development is a series of trade-offs

You know your application better than any of us do. You may not need the scalability that comes with functional style programming. There is a world between the two ideals listed above. Those of us who deal with systems that need to handle high throughput and ridiculous parallelism will tend toward the ideal of functional programming.

That said, you can use data objects to hold the set of information you need to pass in to a method. That helps with the cognitive load problem that Uncle Bob was addressing, while still supporting the functional ideal that David Arno was addressing.

I've worked on both desktop systems with limited parallelism required, and high throughput simulation software. They have very different needs. I can appreciate well written object oriented code that is designed around the concept of data hiding you are familiar with. It works for several applications. However, it doesn't work for all of them.

Who's right? Well, David is more right than Uncle Bob in this case. However, the underlying point I want to underscore here is that a method should have as many arguments as make sense.

Related Topic