Is C++ Ready for Functional Programming? – Everything is a Function

Function

When all you have is a hammer, then everything looks like a nail. And when you are a functional programmer, everything looks like – guess what – a function!

In FP languages most constructs are functions as well, in the sense that you can get a value out of them.

Just to remain on a familiar ground, consider the C++ ternary operator –

int x = b ? a : b;

This is basically an if statement with a return value. It could have been defined as well as –

int x = if( b ) a else b;

Given or taken a couple of semicolons.

Now, pretending that all statements produce a result may seem a whim of functional programmers, but it is really something that makes sense in the functional approach. In fact, if statements do not produce values, how do I transmit the partial result from a statement to the next one? The only way is to use side effects. You need to set a variable in the if, examine it in the for loop below, break on a condition and set another variable that is considered in the switch further below… you get the picture.

We are still within the function boundaries, therefore these side-effects are a lesser evil, nonetheless, the less evil is still… evil. This way of communicating is cumbersome, error-prone, and absolutely not functional.

Unfortunately, the ternary operator is the only C++ functional statement. Well, in fact, there is another, that should have been better not – the assignment. I claim that assignment would have been better designed as not being a function because … Raise your hand those who have never checked equality with a single equal? This happened too many times to me despite always having the maximum warning level.

The lack of this kind of construct is a severe shortcoming in the C++ implementation of functional programming, also because it is hard to build workarounds. Sometimes syntactic sugar may be the difference between usability and unreadable mess.

Just for sake of discussion, without plunging too deeply into the semantics, let’s see some ideas for C++ functional statements.

The first problem is how to identify the return value. In other languages, the last evaluated expression in a statement is the value of the statement itself. This is not really applicable to C++, since programmers are used to ignoring return values. The return statement is used to return from functions, so it is not really intuitive.

switch and loops may return values by extending the break semantic. E.g.

auto foo = switch( bar )
{
    case ONE:
        break 1;
    case TWO:
        break 2;
    default:
        break 42;
};

for and while loops need a way to return a value if the loop exits through its natural end condition. The default result could be placed right after the closing parenthesis with the arrow, much like the functions declared with auto return type:

bool exists = for( auto x: container ) -> false
{
    if( p(x) ) break true;
};

In Scala, for can be decorated with a yield clause, that empowers the for with map/flatMap/filter powers. Let’s have a closer look:

val b = for{ a <- container } yield { 2*a }

This code takes a container and transforms its items, one by one by applying the function after the yield. I.e. it is equivalent to:

val b = container.map( _*a )

But, filtering can be applied:

val b = for{
    a <- container if a < 10
} yield {
    2*a
}

This filters away item equal or greater than 10, being equivalent to the following:

val b = container.filter( _ < 10 ).map( _*a )

Aside from writing the same thing in a different way, the for construct is handy for combining together several operations that may fail (see also my post on comprehensive for). Consider authenticating to an on-line service, retrieving a file and save it locally, and returning the number of lines in the file. The operations you have to perform are: authenticate, download, and save and count lines. All these functions return an Either so that they could be combined in the following chain (of death):

  connection.authenticate(user,passwd).map( _ -> connection )
  .flatMap( connection -> {
      _.download( remoteName )
  })
  .flatMap( saveFile( localName, _ ))
  .flatMap( countLines( _ ))

This can be more conveniently and more clearly written as –

for{
  _ <- connection.authenticate( user, passwd )
  content <- connection.download( remoteName )
  _ <- saveFile( localName, content )
  lines <- countLines( content )
}
yield {
  lines
}

Since I already have ranted against C++ lambda syntax, I leave it as an exercise for the reader to write the equivalent code in C++, given flatMap and map functions.

Instead I’d like to write the same code without the functional approach –

Either<Error,int> countResult = ??? // invent something

auto authResult = connection->authenticate( user, password );
if( authResult.isRight() )
{
    auto downloadResult = connection.download( filename );
    if( downloadResult.isRight() )
    {
        auto fileContent = downloadResult.getRight();
        auto saveResult = saveFile( fileContent );
        if( saveResult.isRight() )
        {
            countResult = countLines( fileContent );
        }
        else
        {
            countResult = Either<Error,int>::Left( saveResult.getLeft() );
        }
    }
    else
    {
        countResult = Either<Error,int>::Left( downloadResult.getLeft() );
    }
}
else
{
    countResult = Either<Error,int>::Left( authResult.getLeft() );
}

Note that initializing the result (countResult) is not a trivial task – you have to fake up an error, that won’t be used at all.

So, let’s consider idiomatic C++, and let’s employ exceptions. For sake of example, let’s pretend that invoked functions return the result or generate an exception. Exceptions are a jungle on their own – do every function throws an exception that inherits from a common base? Does the exception has semantic within its type or also include an error code/error condition?

The code below may be considered a bit of a stretch, but it is not that uncommon on fronteer code when one error handling mechanism needs to match a different one.

int lines = 0;
Error error = NoError;
try
{
    auto connection = connect( server );
    try
    {
        connection.authenticate( user, password );
        try
        {
            auto file = connection.download( filename );
            lines = countLines( file );
        }
        catch( ... )
        {
            error = DownloadError;
        }
    }
    catch{ ... }
    {
        error = AuthenticationError;
    }
}
catch( ... )
{
    error = ConnectionError;
}

It could be slightly different, but dealing with extensions is always a bit touchy.

Functional statements is an area where C++ is quite lacking. It’s fine that FP has been added as an afterthought, but the lack of functional statements is a serious hampering of functional programming in C++. If C++ wants to propose itself as a language for the functional programming paradigm, then it has to take some serious actions in this direction.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: