(Note: This is the fifth part of a whitepaper I wrote a couple of years ago which I never had a chance to publish in full.  You’ll find the earlier parts here, herehere and here.)

Benefits

The main benefits of using functional languages stem from their natural expressiveness and the downstream advantages derived from immutability. Languages that are naturally expressive and favour immutability help fight the accidental complexity that inevitably creeps into large codebases.   These benefits also increase as codebases mature and grow.

Expressivity

Naturally expressive languages allow developers to write less code. Less code is beneficial[1] because:

  • There is less code to understand
  • There is less code to maintain
  • There is less code to test
  • There are fewer places for defects to occur

RedMonk used quantitative analysis of updates to open source code repositories on GitHub to rank the expressiveness of languages (http://bit.ly/1gENQ8y). The theory behind this study is that more expressive languages will result in smaller, more isolated changes for each update, leading to less lines of code being changed (e.g., smaller deltas on average) on each update.

See the charts below for our summary of the Redmonk results comparing the various families of language against the number of times they appear in the most expressive 50% of the results or the least expressive 50%. Screenshot 2016-04-21 19.56.47

Screenshot 2016-04-21 19.56.59

Functional languages rank highly on the highly expressive end of the axis; Clojure (#7), Haskell (#10), Racket (#11), Dylan (#12), Emacs Lisp (#14), R (#17), Scala (#18), OCaml (#20), F# (#21), Erlang (#22) and Common Lisp (#23) consume almost half of the top 23 spots on the graph, whereas Java (#44) and C# (#45) are towards the end of the 52-language list. Whilst the conclusions drawn are subject to lots of uncontrolled variables, the results do correlate with the prevailing impression that functional languages naturally produce more expressive code than their object-oriented equivalents.

Immutability

For many developers, immutability becomes an end unto itself, and the core reasons for the benefit are often forgotten. We mentioned previously that programs with immutable data are inherently easier to reason about because time is no longer a variable that can impact the state of the system. And immutable data structures have another huge advantage that is becoming more and more important with enterprise software:

Immutable objects are naturally thread safe

Immutable objects/data structures provide a consistent view to the outside world at all times after construction. Under this context, multiple threads will see the same version of these objects at all times and avoid classic multi-threaded problems like race conditions and deadlocks that are notoriously difficult to reproduce and debug.

Thread safety is becoming increasingly important as CPUs are built around more, comparatively low powered cores. Software must be capable of running in parallel across multiple cores to take advantage of these architectures.

Counter-intuitively, immutable data structures are usually quite performant, although this was not always the case. When new data structures are created, they share as much structure as they can with the source data structure, rather than automatically taking a separate copy of the source data.

Object creation and garbage collection are not “free” operations so if memory consumption and runtime performance are absolutely critical non-functional requirements, you might want to look at languages that give you more control over these things (e.g., C or C++).   However, the need for this optimisation should be proven rather than anticipated as the benefits of low-level control over memory management are usually outweighed by the ongoing cost of living with code written in such languages. This sentiment is embodied by the well-worn approach of “make it work, make it right and, finally, make it fast”.

Using immutability with OO languages

The value of immutability can also be seen in the amount of effort expended in applying it with languages that don’t typically encourage it. For many years, software engineers have realized the benefits immutable data structures bring in terms of reducing complexity in applications. Therefore, many people have codified practices to design immutable solutions in non-functional languages.

Indeed, the fundamental notion of encapsulation (one of the main goals of good object-oriented design) recognizes the need for a class to provide strict control over who has access to its state and how that state is changed. Although some of the theory of encapsulation is focused on who can read the state of a class, much of the benefit comes from narrowing who can update that state. In immutable systems, good encapsulation is still desirable, but less so as updates to this state are not allowed.

Many developers now favour building immutable classes within their OO language of choice. This approach results in classes that contain only property accessors (i.e., getters) and not mutators (i.e., setters). With this pattern, classes are initialized only during construction and provide no public access to change their state beyond construction. This practice does not prevent internal mutation from occurring, but developers can apply discipline to return new instances of objects when they have been mutated internally. For example, in an Account class, you can write a method to cancel the existing account:

     public void cancel(CancellationReason reason);

or you could write a version which cancels and returns a newly-created instance of Account:

     public Account cancel(CancellationReason reason);

Unfortunately, when the language does not enforce immutability, the discipline falls to the developers to determine when and how the Account has been changed. It would be easy for a developer to ignore the return value and continue to work with the existing Account object. Conversely, in a functional world, developers naturally deal with immutable structures because that approach is inline with the principles of the language: working against these principles generates friction between the language and the developers.

[1] Be careful of fixating on a single metric because of the unwanted behaviours this fixation could produce.

Advertisements