Practical Functional Programming in JavaScript - Side Effects and Purity (Revised)
Hello . Thanks for stopping by. Today we'll discuss two fundamentally dual qualities of JavaScript functions and systems: side effects and purity. I also demonstrate an approach to organizing programs around these qualities with a couple of functions from my functional programming library, rubico.
A function is pure if it satisfies the following conditions:
- Its return value is the same for the same arguments
- Its evaluation has no side effects
A function's side effect is a modification of some kind of state beyond a function's control - for instance:
- Changing the value of a variable;
- Writing some data to disk;
- Enabling or disabling a button in the User Interface.
Here are some more instances of side effects
- reading data from a file
- making a request to a REST API
- writing to a database
- reading from a database
- logging out to console
Indeed, console.log
is a side-effecting function.
// console.log(message string) -> undefinedconsole.log('hey') // undefined
In pure math terms, console.log
takes a string and returns undefined, which isn't so useful. However, console.log
is very useful in practice because of its side effect: logging any arguments you pass it out to the console. I like console.log
because it only does one thing and does it well: log stuff out to the console. When the most straightforward solutions to real life challenges involve a mixture of side-effects and pure computations at a similar execution time, it's useful to have functions like console.log
that have isolated, predictable behavior. My opinion is it's misguided to try to temporally separate side-effects and pure computations in JavaScript for the sake of mathematical purity - it's just not practical. Rather, my approach is to isolate any side effects to the literal edges of your codebase.
I'll demonstrate with a function add10
with several different side effects. add10
is not pure.
let numCalls = 0const add10 = number => { console.log('add10 called with', number) numCalls += 1 console.log('add10 called', numCalls, 'times') return number + 10}add10(10) /*add10 called with 10add10 called 1 times20*/
add10
has the side effects of logging out to the console, mutating the variable numCalls
, and logging out again. Both console.log
statements have side effects because they use the function console.log
, which has the side effect of logging out to the console. The statement numCalls += 1
also has a side effect because the variable numCalls
is state beyond the control of the function.
By refactoring the console logs and the variable mutation to an outside function add10WithSideEffects
, we can have a pure add10
.
let numCalls = 0const add10 = number => number + 10const add10WithSideEffects = number => { console.log('add10 called with', 10) numCalls += 1 console.log('add10 called', numCalls, 'times') return add10(10)}add10WithSideEffects(10) /*add10 called with 10add10 called 1 times20*/
Keep in mind that while add10
is now pure, all we've done is move our side effects outside the scope of add10
and into the more explicit add10WithSideEffects
. Now we're being explicit about the side effects at least, but it's still a bit messy in my eyes. As far as vanilla JavaScript goes, this code is fine. However, I think we can get cleaner with my functional programming library, rubico.
The functions are simple enough at their core so that if you don't want to use a library, you can take these versions of the functions in vanilla JavaScript. Introducing: pipe
and tap
/* * @synopsis * pipe(funcs Array<function>)(x any) -> y any */const pipe = funcs => x => { let y = x for (const f of funcs) y = f(y) return y}/* * @synopsis * <T>tap(f function)(x T) -> x T */const tap = f => x => { f(x); return x }
- pipe takes an array of functions and chains them all together, calling the next function with the previous function's output. We'll use
pipe
as a base foundation to organize our side effects. - tap takes a single function and makes it always return whatever input it was passed. When you use
tap
on a function, you're basically saying "don't care about the return from this function, just call the function with input and give me back my input".tap
is great for functions responsible for a single side-effect likeconsole.log
. We'll usetap
to separate our side-effects by function.
const logCalledWith = number => console.log('add10 called with', number)let numCalls = 0const incNumCalls = () => numCalls += 1const logNumCalls = () => console.log('add10 called', numCalls, 'times')const add10 = number => number + 10const add10WithSideEffects = pipe([ tap(logCalledWith), tap(incNumCalls), tap(logNumCalls), add10,])add10WithSideEffects(10) /*add10 called with 10add10 called 1 times20*/
We've isolated the console.log
and variable mutation side effects to the edges of our code by defining them in their own functions. The final program is a composition of those side effecting functions and a pure function add10
. To be clear, add10WithSideEffects
is not pure; all we've done is move our side effects out to their own functions and, in a way, declare them with tap
. The goal here is not to be pure for purity's sake, but to have clean, readable code with organized side-effects.
logCalledWith
takes a number and logs 'add10 called with' numberincNumCalls
takes nothing and increments the global variablenumCalls
logNumCalls
takes nothing and logs the global variablenumCalls
All of these functions are singly responsible for what they do. When used with pipe and tap in add10WithSideEffects
, the side effects of our program are clear.
If you have some side effects you'd like to organize, I encourage you to check out my functional programming library, rubico! I'll leave you today with a rule of thumb: if you need to console log, use tap.
Thanks for reading! You can find the rest of this series in the awesome resources section of rubico. Next time, I'll dive deeper into data transformation with map
, filter
, and reduce
. See you next time on Practical Functional Programming in JavaScript - Intro to Transformation
Photo credits:
https://www.pinterest.com/pin/213639576046186615/
Sources:
https://en.wikipedia.org/wiki/Pure_function
https://softwareengineering.stackexchange.com/questions/40297/what-is-a-side-effect
Original Link: https://dev.to/richytong/practical-functional-programming-in-javascript-side-effects-and-purity-revised-420h
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To