Argument Zero

Aaron Dilley
Feb 27, 2019 · 4 min read

Much has been made of the inbox zero approach towards email management. Inbox zero certainly doesn’t guarantee you are a more responsive emailer or a more organized emailer, but it at least incentivizes those habits.

In functional programming, I propose the concept of “argument zero” as a similar proxy/ideal to strive for.

Fewer arguments is better

While this may seem obvious, I don’t think a lot of functions are written with intent. Most are bridges meant to get from A to B and that’s it. Arguments are added as needed with little thought of the impact on maintainability. Consider the following progression:

const greet = () => 'Hello.';const greet = (firstName) => `Hello, I'm ${firstName}.`;const greet = (firstName, lastName) =>
`Hello, I'm ${firstName} ${lastName}.`;
const greet = (firstName, lastName, age) =>
`Hello, I'm ${firstName} ${lastName} and am ${age} years old.`;

The final iteration is certainly more specific/personalized, but the arguments are problematic. Which should come first? What should the overall order of these arguments be? Does adding arguments change this order? In this example, the arguments can be consolidated to a single object argument:

const greet = (person) =>
`Hello, I'm ${person.firstName} ${person.lastName} and am ${person.age} years old.`;

This way, even if the underlying data structure changes, the instances of greet shouldn’t. The function also becomes more declarative about what is needed to execute this greeting.

Testing

One benefit of argument reduction is how much simpler your unit tests become. Fewer arguments means less surface area for bugs to live. Of course there’s still the risk your object values are improperly formatted, but consolidating arguments this way also sets up an easier transition to TypeScript which can handle your type validation leaving your tests to focus more on functionality and less on edge cases.

The dreaded options object

The above guidance is often abused to shove miscellaneous arguments into a single options object. These options are seldom documented or restricted to any more specific context. That’s not to say that config or options objects aren’t ever appropriate, the question is really whether there’s a clear answer to “Options for what?” More often than not this is a sign the function has grown large in scope and should be broken down and decoupled.

But this is 1 argument, not 0 arguments

The actual goal in this approach is argument consolidation and decoupling where appropriate. And it is still perfectly acceptable for functions to accept an indefinite number of arguments:

const sum = (...nums) => nums.reduce((sum, n) => sum + n);const pipe = (...fns) => x => fns.reduce((f, g) => g(f), x);

Note in each of these examples not only are all arguments assumed to be the same type, but are consumed as a single data type within the function itself. In these cases, as an array. If we wanted to we could just as easily write:

const sum = (numArray) => numArray.reduce((sum, n) => sum + n);const pipe = (fnArray) => x => fnArray.reduce((f, g) => g(f), x);

What I actually mean by argument zero, are functions that can be defined without specifying any arguments whatsoever, often by referencing an existing function. Consider the following promise callback:

// fetchData is a promise
fetchData().then(data => console.log(data));

We’re already in a good spot because our promise returns a single data object. However, we can save some characters and add some context by distilling this to:

fetchData().then(console.log);

This is both simpler and more declarative than the first iteration. console.log will take all arguments passed to it and log each in sequence; I don’t actually need to specify what is going to be logged.

Currying with Ramda

Once your functions are organized in this way, they are setup nicely for functional programming libraries like Ramda where every single function offers currying by default. What’s so nice about curried functions is how composeable they are without needing to actually specify the target data structure. Consider the following example in vanilla JavaScript:

// example log: '127.0.0.1 - - [24/Mar/2018 20:08:10] "GET / HTTP/1.1" 200 -'
const parseLogs = logs =>
logs.filter(log => log.includes('GET'))
.map(log => log.match(/\d{2}\/[A-z]{3}\/\d{4}/)[0]);
parseLogs(['127.0.0.1 - - [24/Mar/2018 20:08:10] "GET / HTTP/1.1" 200 -']); // ['24/Mar/2018']

This is actually not too bad. filter and map are declarative enough. But we end up having to chain off of the logs array and then further declare the individual log variables. The following is functionally equivalent in Ramda:

const parseLogs = R.pipe(
R.filter(R.includes('GET')),
R.map(R.pipe(
R.match(/\d{2}\/[A-z]{3}\/\d{4}/),
R.head)
)
);
parseLogs(['127.0.0.1 - - [24/Mar/2018 20:08:10] "GET / HTTP/1.1" 200 -']); // ['24/Mar/2018']

Not once are logs or log referenced, and yet the declarative and composeable qualities are preserved.

While Ramda solutions can be verbose at times, I find it’s easier to parse intent as no intermittent variables are introduced to interrupt the composition flow. It reads almost exactly like pseudocode which makes this code maintainable and extensible.

Ramda is a powerful library that takes some getting used to especially for those new to functional programming. But mastering it is well worth your time and eliminates the need to author common utility methods from scratch.

Conclusion

Don’t spend hours refactoring your entire codebase in this way, but do try to integrate into future code. It will force you to write smaller, single purpose functions. You’ll thank yourself in the long run.

Aaron Dilley

Written by

Front End Developer and Crossword Enthusiast

More From Medium

Also tagged JavaScript

Also tagged Functional Programming

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade