This is part 1/17 of my Exploring the .NET CoreFX series.
A pure method is a method that does not make any visible state changes.
John Carmack, in his article In-Depth: Functional Programming in C++, notes many advantages of pure functions:
Pure functions have a lot of nice properties.
Thread safety. A pure function with value parameters is completely thread safe. With reference or pointer parameters, even if they are const, you do need to be aware of the danger that another thread doing non-pure operations might mutate or free the data, but it is still one of the most powerful tools for writing safe multithreaded code.
You can trivially switch them out for parallel implementations, or run multiple implementations to compare the results. This makes it much safer to experiment and evolve.
Reusability. It is much easier to transplant a pure function to a new environment. You still need to deal with type definitions and any called pure functions, but there is no snowball effect. How many times have you known there was some code that does what you need in another system, but extricating it from all of its environmental assumptions was more work than just writing it over?
Testability. A pure function has referential transparency, which means that it will always give the same result for a set of parameters no matter when it is called, which makes it much easier to exercise than something interwoven with other systems. I have never been very responsible about writing test code; a lot of code interacts with enough systems that it can require elaborate harnesses to exercise, and I could often convince myself (probably incorrectly) that it wasn’t worth the effort.
Pure functions are trivial to test; the tests look like something right out of a textbook, where you build some inputs and look at the output. Whenever I come across a finicky looking bit of code now, I split it out into a separate pure function and write tests for it. Frighteningly, I often find something wrong in these cases, which means I’m probably not casting a wide enough net.
Understandability and maintainability. The bounding of both input and output makes pure functions easier to re-learn when needed, and there are less places for undocumented requirements regarding external state to hide.
Another advantage of pure methods, as noted by this LWN article, is that the compiler can apply higher optimization than normal to the method. Two such optimizations that may be applied are common subexpression elimination and dead code elimination.
Here’s some sample code from System.Collections.Immutable which uses PureAttribute:
- Annotate pure methods with System.Diagnostics.Contracts.PureAttribute.