Why Use Functional Programming Features?

Posted on May 26, 2016 by Richard Goulter
Tags: , ,

I was asked in an interview “what advantages are there to coding in a functional style?”.
I’d use ‘functional’ a bit more loosely than “everything pure, monads, etc.”; I’m happy with modern language idioms like pattern matching.
– I’d say the best reason is it makes explicit dangerous things which are implicit in procedural, blub language code.

Consider a typical dictionary.get(key):
If the key is in the dictionary, get the value, the world is at peace and all is well.
But writing code where things go well is easy. Here, it’s more interesting to consider how to model “what to do when things go wrong”. – To me, returning Optional[Value] (as any “functional” language will do) seems the most elegant:

Mainstream languages (like Java, Python) tend to return null/None for this case.
Mainstream languages also tend to throw errors when trying to use a null-value as if it’s not a null-value. So what happens is you’ll write code assuming the value is non-null; at some point this (implicit) assumption will be wrong. (Let he who’s never had a NullPointerException throw the first stone).

Optional[Value] is kinda morally equivalent. A dictionary returns Some[Value], which contains a non-null value if the dictionary has the key, None if the key wasn’t present in the dictionary. (Well, technically it could be a Some(null); but by the same argument, this’d be better as Some(None)).
The syntactic difference is, every time you have an Optional[Value] you MUST have optional match { Some(x) => ...; None => ...; } (or an acknowledgement of ‘danger’, like optional.getValue()). – With nullable variables, it’s up to the programmer’s discipline to have if (x != null) { ... } else { ... }; I’d be willing to bet the else clause is almost-always missing in code.
– Or, rather, looking at code where values are never null, you know you can’t get NPEs. With nullable-values, you’ve to rely on (implicit) assumptions, and maybe-probably the code is fine.

There are other ways to deal with errors.
One kinda-stupid approach would be for the dictionary to loop indefinitely (until it has a value for the given key). In a concurrent/parallel program, this isn’t completely stupid, & blocking-until-result may be desirable. But still.

Throwing an exception is another approach. Python does this. It seems to me this is kindof-sortof morally equivalent to Optional[Value] also; the benefit being that a value isn’t going to be null, the drawback being it’s difficult to tell what the program is doing:

try {
  x = dict.get(keyX);
  y = dict.get(keyY);
  doTask2(x, y);
} catch KeyNotPresent ex {

– Obviously if an exception is thrown above, it’s not clear whether it came before or after doTask1(x); perhaps less clear is that y = dict.get onwards ‘needs’ its own try-catch block. It doesn’t make sense to have KeyNotPresent exception cover both x = dict.get and y = dict.get; not if you want to be sure that you’re handling each alternative.

Error Codes, similarly, seem kinda-sorta equivalent if your program follows discipline to handle all alternatives. Somehow Exceptions are seen as good for allowing you to “leave error handling code out of much of your code”. (If you don’t write half your program logic, you don’t have to write half your program logic). – Though, much of the advantages of Exceptions over Error Codes here also apply to Optional and friends.

– The advantages that Exceptions and Error Codes have over Optional[Value] is that the latter just says “not everything went well”, the former can describe what went wrong. (IOError, DivisionByZero, StackUnderflow, UnsupportedOperation, etc.). This can be quickly remedied by using Either, i.e. keeping information about what kind of error occurred.

That’s all fine & well.
Optional works well to model where computations may fail; Either for when you want to know why things went wrong (or perhaps maybe to recover depending on what went wrong). These are more explicit than nullable values or Error Codes.

The bigger challenge is how well functions-which-may-fail compose with other functions-which-may-fail.

fun importantComputation() {
  x = mayFailX()
  y = mayFailY()
  return g(x, y);

With stateful code, one needs to be careful about assumptions about what stmt1, mayFailY mutate. – Implicitly, you also mustn’t assume stmt2, g are executed (& so must consider state when they are executed, aren’t executed).
– While it’s true that Exceptions defer the error-handling; there’s surely more cognitive-load required to keep the above in mind while scrolling through code.

Functional code also isn’t necessarily so cumbersome. Optional.map allows:

optional match {
  Some(x) => doSomething(x)
  None => None

to be written as optional.map({ x => doSomething(x) }) (or .map(doSomething) if you literally have a function).

Programmers tend to make mistakes when they forget to take care of cases when things go wrong.
It’s easier to make mistakes when “something went wrong” is implicitly handled.
Functional Programming-ish code tends to be very explicit; and potentially more concise. For writing correct, complete, safe code, this is clearly an advantage.

Newer post Older post