Given the opportunity to revisit my Genetic Algorithms library, I wanted to revisit my open letter to Jon Skeet, about semantics and dynamic languages, and compare how the code looks in C++ vs Python. I accept that C++ is an overly verbose language but the problem I want to discuss is similar in C# and Java. I’m just going to pick a few examples, but feel free to explore the code yourself, either as C++ templates or as Python examples. These examples all expect a certain knowledge of Genetic Algorithms, so I’d recommend reviewing my previous post first if you are unfamiliar with the concepts.
If you’re trying to compare them directly from the repositories, please note that I have removed some comments and logging, and changed the formatting in this post for reasons of clarity, and have yet to apply those changes back to the repository. I have also added some functionality to the python code that doesn’t currently exist in the repository to ensure the examples are comparable.
Fitness
In C++
/*********************************************************************** * SAMPLE FITNESS FUNCTIONS * ***********************************************************************/ template<class _Ctype, int _Csize> struct fitness_base { virtual double operator()(_Ctype chrom[_Csize]) = 0; } ; template<class _Ctype, int _Csize> struct maxones : public fitness_base<_Ctype,_Csize>{ double operator()(_Ctype chrom[_Csize]) { return std::count(chrom, chrom+_Csize, 1); }; } ;
The fitness function for a chromosome returns a number that is used to rank the chromosome such that fitter solutions are more likely to survive and breed. For the example below, note that by default, the chromosome is an array of type _Ctype
(default: bool
), of length _CSize
(default: 32
)
The fitness function must be passed as a parameter to the constructor so that different problems can be explored using the same representation. This requires a type for fitness functions fitness_base
which is defined as virtual
to ensure it can be overridden, and contains an () operator so it can be called. In C#, this would be a delegate type, and we would be able to use the function/action types from the library as the base class for maxones
. The fitness function shown simply counts the number of ones in the chromosome.
In Python
def fitness(self): return sum(self.genome)
Functions are first-class objects in Python, so we don’t need anything special to pass them as arguments, or require overloading the () operator. However, Python also allows patching classes post-initialisation, so we can overwrite the fitness function, or we can use Python’s multiple inheritance mechanism to subclass 2 different versions of chromosome, one of which overrides the fitness function, and the other overriding the crossover function.
Note that the fitness function is different between the two fitness functions is simple, but I now prefer the cleanliness of sum
to a conditional count
, but the overall character of the code will not be altered by the 2-character difference.
Run one generation
In C++
void run_once_replace() { // one generation = psize crossovers pop_t newpop; newpop.reserve(_population_size); for(int i = 0; i < _population_size; ++i) { chrom_t child = select_chromosome() + select_chromosome(); newpop.push_back(child); } copy(newpop.begin(),newpop.end(),_population.begin()); };
In Python
def run_once(self): "Run one iteration of the genetic algorithm" # Use replacement algorithm new_pop = [] for i in range(len(self.pop)): a, b = self.select_two_members() new_pop.append(a + b) self.pop = new_pop
In these examples, apart from some small syntactic sugar that allows us to return 2 values from a function, the C++ and Python code are just as clear as each other.
Conclusion
In contrast to my letter, there isn’t as big a difference as I believed when I originally wrote the letter, at least in terms of the resultant code, although I remember the pain trying to get the types lining up properly in the first place. Semantics still matter, and types definitely got in the way designing the chromosomes, but actually, outside some specific cases, with well structured code, static and dynamic languages can both make semantic meaning clear.