Functional programming: A step backward

| July 5, 2012

Originally posted at Infoworld by Andrew Oliver, this is article is a little controversial, especially if you are a fan of functional programming, which is all the rage right now; that said, it's interesting to read his take on it considering that he isn't not exactly a fan.

Functional programming languages will have a place in general application development when we can read their code at a glance

Unless you've been living under a rock, you know functional programming is all the rage among the so-called alpha geeks. Perhaps you already use a functional programming language. If you use a more conventional language like Java or C#, you're probably aware it has functional programming features in store. While this brave new world is upon us, and before we take things too far, it might be a good time to pause and reflect on the appropriateness of functional programming for everyday application development.

What is functional programming? The simple answer: Everything is a mathematical function. Functional programming languages can have objects, but generally those objects are immutable -- either arguments or return values to functions. There are no for/next loops, as those imply state changes. Instead, that type of looping is performed with recursion and by passing functions as arguments.

The case for functional

Proponents often argue that functional programming will lead to more efficient software, while opponents argue the reverse is true. I find both claims equally doubtful. I could easily be convinced that functional programming will make writing compiler optimizers more difficult or that the JIT compiler for functional code will be slower than the equivalent compiler for traditional code. There are close mappings between imperative programming languages and the underlying hardware support, but these don't exist for functional languages. As a result, the compiler for a functional language has to work harder.

However, a good optimizer should be able to translate a functional programming closure, tail call, or lambda expression into the equivalent loop or other expression in a traditional language. It may require more work. If you're up for a good 1,600 pages of reading on the subject, I recommend "Optimizing Compilers for Modern Architectures: A Dependence-based Approach" and "Advanced Compiler Design and Implementation." Alternatively, you can prove this to yourself with GCC or any compiler that has multiple front ends and can generate the assembler.

The better argument for functional programming is that, in modern applications involving highly concurrent computing on multicore machines, state is the problem. All imperative languages, including object-oriented languages, involve multiple threads changing the shared state of objects. This is where deadlocks, stack traces, and low-level processor cache misses all take place. If there is no state, there is no problem.

There are many places where functional programming and functional programming languages are a great fit and probably the best approach. For pure mathematical calculation, functional programming is actually clearer than imperative programming. But for business software and other general application software, exactly the opposite is true. As Martin Fowler famously said, "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." The syntax of functional programming just isn't readable at a glance.

A couple of code snippets will show you what I mean. An example from Erlang:

by_length(Lists) ->
 qsort(Lists, fun(A,B) -> A < B end).
qsort([], _)-> [];
 qsort([Pivot|Rest], Smaller) ->
 qsort([X || X <- Rest, Smaller(X,Pivot)], Smaller)
 ++ [Pivot] ++
 qsort([Y || Y <- Rest, not(Smaller(Y, Pivot))], Smaller).
-- file: ch05/Prettify.hs
 pretty width x = best 0 [x]
 where best col (d:ds) =
 case d of
 Empty -> best col ds
 Char c -> c : best (col + 1) ds
 Text s -> s ++ best (col + length s) ds
 Line -> 'n' : best 0 ds
 a `Concat` b -> best col (a:b:ds)
 a `Union` b -> nicest col (best col (a:ds))
 (best col (b:ds))
 best _ _ = ""
          nicest col a b | (width - least) `fits` a = a
 | otherwise = b
 where least = min width col

Man versus machine

Any halfway decent programmer can quickly glean the general intent of most imperative code -- even in a language he or she has never seen. While you can certainly figure out what functional routines do by looking at them, it may not be possible in a glance. Unlike imperative code, functional code doesn't map to simple language constructs. Rather, it maps to mathematical constructs.

We've gone from wiring to punch cards to assembler to macro assembler to C (a very fancy macro assembler) and on to higher-level languages that abstract away much of the old machine complexity. Each step has taken us a little closer to the scene in "Star Trek IV" where a baffled Mr. Scott tries to speak instructions into a mouse. After decades of progress in making programming languages easier for humans to read and understand, functional programming syntax turns back the clock.

Functional programming addresses the concurrency problem of state but often at a cost of human readability. Functional programmming may be entirely appropriate for many circumstances. Ironically, it might even help bring computer and human languages closer together indirectly through defining domain-specific languages. But its difficult syntax makes it an extremely poor fit for general-purpose application programming. Don't jump on this bandwagon just yet -- especially for risk-averse projects.