Purity is Great for Collaboration

When people talk about the benefits of purity in functional programming, the benefits that are often mentioned are equational reasoning, ease of testing, memoization and ease of parallelization. To me, these are all great benefits, but might not be enough to convince a skeptic.

What I often do find missing in these conversations is how much of a benefit purity can be to collaboration, specifically for dependencies. Purity is a contract that says: I will not do any kind of I/O; I.E. no network calls, no db calls, no file reading, no reading environment variables etc. That means that third-party dependencies won’t do any of these things, because if they try, the program won’t compile.

Today, software is probably more vulnerable than ever to supply chain attacks, especially in languages like JavaScript and Rust that have a culture of importing dependencies for every small problem they have.

How supply chain attacks can occur.

As an added negative in JavaScript, auditing code for exploits and defects is practically impossible. The code uploaded to npm is often minified to reduce asset size, and it does not have to match the source code hosted on a VCS. To audit, you would therefore often have to review minified code, which is very hard to read. I have never met anyone who does that.

Some might argue that this is a cultural issue and that we need to just stop depending on so many things; a dependency should be brought in seldom and if it is brought in, then it should be audited. It could also be argued that importing lots of dependencies adds bloat to projects. I sympathize with this sentiment. Even so, the solution to “stop using so many dependencies” is incredibly hard to implement, and also gives up the productivity gains of reusing already-built resources.

In elm, a pure functional language, auditing code for security vulnerabilities is a lot easier. There are basically two places where a supply-chain attack could occur in elm code: in functions that have Html or Cmd in their signature, as those are the two signatures that allow a function to have side effects. So when you audit a dependency, you can basically Ctrl+f Cmd|Html to find the functions that do side effects, and focus your efforts on auditing those functions. The code that has those signatures tend to be extremely simple because of the culture around pure languages, so reviewing it is easy. You’d see clearly if there is any attempt to do a network call or other suspicious activity. Barring some kind of exploit, the pure functions in the dependency would not be able to send credit card details or other unwanted behavior. The only attack that I can think of with pure functions are creating an infinite loop, divide by zero, or somehow providing broken cryptography algorithms. This mitigates the risk of supply chain attacks significantly, and actually makes auditing code achievable.

Finally, since pure functions can not be redefined or removed in a pure language, which enables dead code elimination on a function level to remove unused bloat from your dependencies and vastly reduce the compiled asset size. The equivalent in JavaScript, tree shaking, can not reach anywhere near that level of optimization. You can see elm’s result of asset size optimization in their blog post from 2018. And as a cherry on top, my personal experience is that even with all optimizations in place, elm still manages to compile fast, on a level similar to that of go.

How elm handles asset size optimization