Functional holes

Created 11th July, 2007 09:29 (UTC), last edited 11th July, 2007 10:48 (UTC)

Brian over at Enfranchised Mind always has something interesting to say. Just recently it seems he's been grappling with C# and trying to work out a good abstraction for a particularly recalcitrant COM object.

The first problem he has is here:

Thus I was coding this in C#, because of the three possible languages, VB.NET, C#, and C++, I disliked C# the least.

This is his first error. He's chosen his implementation language based on personal preference rather than suitability to the task.

I'm sure that Brian has his reasons for disliking C++, but in this case his dislike has cost him dear. It turns out that C++ has the abstractions that he misses from OCaml and the solution that he's after is an almost trivial use of them. Let's take a look at it.

Here is a function not entirely unlike the sort of thing that he's struggling with:

void print( const std::string &what, bool &succeed ) {
	if ( !succeed ) {
		succeed = true;
		throw std::invalid_argument( "succeed is false" );
	}
	std::cout << what << std::endl;
}

We can invoke it in one of two ways:

bool f( false ), t( true );
print( "Hello", t ); // works
print( "Hello", f); // throws an exception

This simply allows us to control whether or not it throws. Notice that if we can call it a second time then it will always succeed because it sets the second parameter's value to true before throwing the exception.

Here is the sort of thing that he needs for his attempts and their recovery:

template< typename R >
R execute( boost::function< R( void ) > f ) {
	int count( 0 );
	while ( true ) {
		try {
			return f();
		} catch ( std::invalid_argument &e ) {
			if ( ++count > 3 )
				throw;
			std::cout << "Caught exception... " << e.what()  << " ...retrying" << std::endl;
			// Recovery procedure
		}
	}
}

It's a simple function that takes another function to try up to three times. If you've not done any functional programming in C++ then the type boost::function< R( void ) > probably looks a little odd. It simply describes a function that returns an R and doesn't take any arguments.

So how do we get one of these function objects? The easiest way is to use a lambda. We're going to use something akin to partial function application, but we're going to apply all of the parameters in one go but not execute the function. Here is how to do that:

		bool local_var( false );
		boost::function< void ( void ) > functor( boost::lambda::bind( print, "Hello world", boost::ref( local_var ) ) );

The variable functor now contains our lambda which we can execute whenever we like by just calling it:

functor();

Here is a complete listing that you can play around with* [*I've been using the new Boost 1.34.0 release candidate and Microsoft C++ 7.1, but it should work on pretty much anything.]

#include <boost/function.hpp>
#include <boost/lambda/bind.hpp>
#include <iostream>
#include <stdexcept>

void print( const std::string &what, bool &succeed ) {
	if ( !succeed ) {
		succeed = true;
		throw std::invalid_argument( "succeed is false" );
	}
	std::cout << what << std::endl;
}

template< typename R >
R execute( boost::function< R( void ) > f ) {
	int count( 0 );
	while ( true ) {
		try {
			return f();
		} catch ( std::invalid_argument &e ) {
			if ( ++count > 3 )
				throw;
			std::cout << "Caught exception... " << e.what()  << " ...retrying" << std::endl;
			// Recovery procedure
		}
	}
}

int main() {
	try {
		bool local_var( false );
		boost::function< void ( void ) > functor( boost::lambda::bind( print, "Hello world", boost::ref( local_var ) ) );
		std::cout << "About to try" << std::endl;
		execute( functor );
	} catch ( ... ) {
		std::cout << "Exception caught" << std::endl;
	}
}

The output is this:

About to try
Caught exception… succeed is false …retrying
Hello world

Many people dislike C++ for what they think it is, rather than what it actually is. There's plenty to dislike about it as a language, but don't let that fool you into thinking that it's power and expressiveness is in the same league as Java or C#.