States and complexity in programming – why functional programming helps to improve your code

The human brain is very, very bad at keeping track of states. Especially for code maintenance states are hell, in particular if you are not the original author of the code, and even then. Even if there is ample and up to date documentation around, trying to understand states of a program is very difficult. States don’t match well to the way the human brain is thinking. Computers, however, excel at it. On the other hand, humans are quite good at abstracting functionality, finding commonalities and patterns as well as following an explicit program flow, in particular if the code uses telling function and variables names and generally a coherent coding convention. Abstraction and pattern recognition in code is something that computers don’t do really well at all.

A programming language should therefore reflect this observation and provide efficient tools to use this human ability. Functional programming is a thinking tool and programming philosophy that helps the natural tendencies of the brain to abstract. While functional programming can be applied in practically every language that supports functions (essentially every modern programming language), some languages try to provide suitable and expressive syntax that allow to move much further up on the abstraction scale whereas other languages, allow only elementary functional programming and make life for the programmer more difficult than necessary.

Two concepts,which go hand in hand to apply the full power of functional programming are

  • first class functions (this implies: anonymous functions/lambdas, higher order functions (HOF))
  • dynamic types

In statically typed languages like C for example it is possible to use first class functions, but they can be used only very clumsily, since the functions always carry around their statically typed signature, which means that a higher order function must be specialized for every single signature.

In C++ templates allow a slight circumvention of the rigid type system, however the code looks even more clumsy.

For example in C you could write for a trivial example of using function pointers:

#include <stdio.h>

int add(int a,int b) {return(a+b);}
int mul(int a,int b) {return(a*b);}
int compose (int (*f1) (int,int), int (*f2) (int, int),int a,int b,int c)
{  return (*f1)((*f2)(a,b),c);
}

int main(int argc,char** argv)
{ int a=compose(add,mul,1,2,3); /* => 5 */
  int b=compose(mul,add,1,2,3); /* => 9 */
  fprintf(stderr,"%d %d",a,b);
  return(0);
}

This is probably as complex as you can go in C. Essentially you are limited by the fact that you cannot create new functions at runtime. Thus, while in C/C++ functions are somewhat first class citizens in that they can be assigned to a variable or can be used as function arguments, they have important and significant limitations.

In C++ the use of templates makes it only partially more versatile in that you can specialize functions at compile time for different types:

#include <stdio.h>

template <class T>  
      T compose (T (*f1) (T,T), T(*f2) (T, T),T a,T b,T c) { return (*f1)((*f2) (a,b),c); }
template<class T> T add(T a,T b) {return(a+b);}
template<class T> T mul(T a,T b) {return(a*b);}

int main(int argc,char** argv)
{ double a=compose<double>(add<double>,mul<double>,1.1,2.2,3.3);  // => 5.72
  double b=compose<double>(mul<double>,add<double>,1.1,2.2,3.3);  // => 10.89
  int c=compose<int>(add<int>,mul<int>,1,2,3); // => 5
  int d=compose<int>(mul<int>,add<int>,1,2,3); // => 9
  fprintf(stderr,"%f %f %d %d",a,b,c,d);
  return(0);
}

Is functional programming with higher order functions possible in C/C++? Yes, with a lot of black magic, and a lot of compromises. It it worth it? Nope, not really, at least not the more abstract type of functional programming. The code becomes more difficult to read and probably will introduce difficult to catch bugs. And since it is so difficult, you will most-likely not use this technique on a regular basis. (Exceptions are user provided functions like compare-functions for qsort etc., or in C++ the STL container functions).

The problem is that you cannot separate function-composition and arguments since you cannot create new functions on the fly.

Compare this to dynamically typed languages with some support for higher order functions:

Lua:

add=function(a,b) return(a+b) end
mul=function(a,b) return(a*b) end
compose=function(f1,f2)
    return function(a,b,c) return f1(f2(a,b),c) end
end
f1=compose(add,mul)
f2=compose(mul,add)
f1(1,2,3) -- => 5
f2(1,2,3) -- => 9

This creates a new function on the fly which takes 3 arguments a,b,c on a closure of the function arguments, which are then applied in sequence.

Thus you can create this newly composed functions, store them in the variables f1 and f2 and run with them.
The same way this can be done in Scheme/Lisp.

Scheme:

(define (add a b) (+ a b))
(define (mul a b) (* a b))
(define (compose f1 f2)
    (lambda (a b c)
        (f1 (f2 a b) c)))
(define f1 (compose add mul))
(define f2 (compose mul add))
(f1 1 2 3) ; => 5
(f2 1 2 3) ; => 9

The same code in Common Lisp (CL) is slightly more convoluted since CL is a lisp-2 system:

(defun add (a b) (+ a b))
(defun mul (a b) (* a b))
(defun compose (f1 f2)
  (lambda (a b c)
    (funcall f1 (funcall f2 a b) c)))    
(setf (symbol-function 'f1) (compose #'add #'mul))
(setf (symbol-function 'f2) (compose #'mul #'add))
(f1 1 2 3) ; => 5
(f2 1 2 3) ; => 9

As you can see even in this most trivial example the support for dynamic types and anonymous functions as first class objects opens a completely new world of abstraction. With these tools, abstraction level become possible that are unthinkable in statically typed languages like C/C++, Java etc. Code becomes more concise and reusable, it is much easier to see patterns and abstract them away. It becomes easier to refactor code. And there are many more advantages to program in a more abstract way.

This is what functional programming does. It allows you to build bigger and bigger building blocks with higher and higher leverage which you can combine in a multitude of ways.

Variables are necessary for computations, that does not mean, that you actually have to use them directly and often in a programming project. A variable holds a data structure. Direct access to the data structure, to the variable, is only recommended on the lowest level.

Hardly anything you program is so low level, that it needs to be expressed directly in terms of a jumble of global variables and states. Use functions instead (and maybe some local helper variables). Let the compiler do its job and work on the proper unfolding.

By using variables directly you deprive yourself from the privilege of adding further transformations to values.

By using variables directly you deprive yourself from the privilege of adding further transformations to values. The power of values is invaluable.

Variables (and this includes classes) make very bad building blocks. They don’t grow. Classes are useful to categorize methods but in the end, in many [commonly used] programming languages the syntax becomes quite quirky (C/C++, Java). Thus modules/namespaces are maybe a better way to go.

The power of values is invaluable.

It is vital to express the problem in the words it can be best described and not in the way the programming language needs it by an imposed programming paradigm. This will most-likely not be in terms of variables and classes but in terms of actions, i.e. functions. Thus you must build up an appropriate vocabulary. (see Essence of functional programming for more details). And a good/suitable programming language should allow you to do that. Unfortunately most mainstream programming languages rather hinder than promote this. Examples where this approach works nicely though are: Lisp/Scheme, Forth, Joy, APL/K/J/Succinct, Haskell. As you can see from the list and not surprisingly these are all functional languages, languages that do not stand in the way of creating functions on the fly and using higher order functions to create more abstract representations of the code.

What can you do if you are constrained and chained to a language that does not allow the use of higher order functions (easily), such as most mainstream languages C/C++, Java etc.? There are work-arounds that allow you to do at least part of what you can do easily in more functional languages, but it is not recommended to use them since it creates more maintenance headache down the road. However, there are simple concepts of functional programming that can be applied in any language.

  1. Use small functions that wrap around a single data structure.
  2. Create a vocabulary of actions with these functions.
  3. Combine those actions in meaningful ways.

If you are familiar with Unix scripting you will have probably used the shell (e.g. bash). The bash provides access to the file system and to a large set of commands. These are usually small utility programs that do one thing very well. What many of those tools have in common is that they act like a filter: they take a stream of data in and output another stream of data. The shell provides a concatenation operator so that you can chain those tools into small programs that are tailored to your specific need. Instead of writing a monster program that tries to do everything, the shell encourages you to reuse existing tools and build a chain of small programs similar to LEGO-blocks to solve your problem. For example:

cut -f1 -d” “ .bash_history | sort | uniq -c | sort -nr | head -n 100

This small program consist of 5 different tools that are used in sequence. The output of each tool is fed as input to the next. (You can read about what this code does here)

In a similar fashion, you can try to create functions that act like filters. For example in a image processing program you can write functions that you stack like above shell commands:

analyze(despeckle(threshold(luminance_gray(invert(load_image(file))),128)))

This code is easy to read and understand, concise and does not use any temporary variables.

In conclusion, using some concepts of functional programming can help you

  • to reduce complexity
  • to write more abstract code
  • to write more reusable code
  • to write shorter code
  • to write code that is easier to refactor
  • to use less variables and write less spaghetti code
  • to create less bugs
  • to write more readable code
  • to write code that is easier to maintain
  • to write code that is less redundant and does not suffer from the copy and paste syndrome.

If you haven’t already, read  up some concepts and techniques of functional programming and try to use them in your next project. You will be surprised at the different ideas and views a more abstract code base will give you. Many concepts of functional programming can be used in any programming language that supports functions, some more abstract concepts such as higher order functions and dynamic typing are, however, reserved for languages that explicitly provide support for functional programming like Lisp/Scheme, Lua, APL, Forth, Haskell, etc.

12 thoughts on “States and complexity in programming – why functional programming helps to improve your code

  1. Pingback: online casino paypal singapore

  2. Pingback: sildenafil 100mg price cvs

  3. Pingback: cialis 5 mg fiyat

  4. Pingback: goodrx sildenafil 100

  5. Pingback: play online slot machines real money

  6. Pingback: coat of sildenafil pills at walmart

  7. Pingback: sildenafil 25 mg online

  8. Pingback: cialis coupon

  9. Pingback: cheap viagra london

  10. Pingback: viagra tablets in india online purchase

  11. Pingback: do old viagra pills still work

  12. Pingback: discount viagra or cialis

Leave a Reply

Your email address will not be published. Required fields are marked *

Current month ye@r day *