Expressiveness in Modern Programming Languages

iStock_000009524688XSmall The Technical Aptitude Required for Object-Oriented Programming

One of the primary purposes of an object-oriented programming language is to recreate representations of solutions to specific problems with code.  These representations are transformations of solutions that were typically solved previously with human interactions.

Sometimes certain problems are solved only with the feat of modern computer software.  In other facets, ingenuous minds forge an inventive technology in software not unlike the ways of the great mechanical inventors of our age.

Traditional object-oriented programming has been one of those great technical inventions.  While simple guidelines allow most programmers to attempt object-oriented programming, only few understand the principles and practices to actually model real concepts correctly.

Frequently, programmers create objects that simply do not exist in the domain, or are bastardizations of the true domain concept.  Unfortunately, in these scenarios success is short-lived.  Maintainability quickly degrades, and often the code is completely thrown away.

Incorrect representations of the domain are similar to the adage of trying to fit a square peg into a round hole.  Perhaps it’s possible to do this with creative technical feats.  However, as more features are requested, as bugs are fixed, and the code evolves, misrepresentations are quickly revealed.

When this happens, feature requests and bug fixes take longer than expected, and a tremendous amount of pressure is put on the development team to meet deadlines.  Managers and executives begin to wonder why it is taking so much time to complete what they think are smaller tasks.

All-nighters, pizza, and an insane amount of coffee support the developers through their death march.  If you have been developing for a decent amount of time, I am sure you have told of such horror stories over lunch or over a good beer.

The Expressiveness of a Language Matters

How do we prevent this pain?  There are several factors that contribute to these unfortunate events, however expressiveness within source code can be an important piece of the puzzle.  The best developers are often the most expressive in the language he or she is operating in.

Instead of engineering solutions using technical wizardry and concepts that only programmers can understand, being expressive within the source code can keep business rules intact as development continues and solutions evolve.

Expressiveness is a quality that developers must constantly be working toward.  Interestingly enough, the choice of a programming language can greatly assist in this struggle.  The more expressive a language is, the easier it is for the programmer to think about the domain.

If non-programmer types and developers communicate with a ubiquitous language, this language can pervade into the source code.  The pervasiveness of a domain’s language allows ideas to be more easily understood.

Contrasting Language Expressiveness

On May 30th, 2009, Scott Bellware gave a talk entitled, “A Handful of Things You Can Do in Ruby That Scares the Pants Off of C# Developers.”  In this fascinating talk, Scott showed two functionally equivalent, plain classes:  one defined in C#, the other defined in Ruby.

An interesting illustration emerged when Scott showed how the C# class’s definition comprised of a lot more statements to instruct the compiler on what to do and how to perform.  These statements take the programmer’s mind away from the domain and bring him or her to thinking in a technical programming wonderland.

On the other hand, the Ruby class contained very little compiler instructions; the class was mostly comprised of domain expressions.  This is a powerful illustration, so I’d like to show it as well as a means of illustrating expressiveness.

Let us define a simple class that records recommendations of businesses.  Recommendations can be used for any business type, such as restaurants.  A ‘recommender’ can recommend a business only once.  The class will allow a means to determine how many recommendations all of the businesses have received.

The C++ Definition (brace yourself)

First, I will define the class using one of the most traditional object-oriented programming languages:  C++.  Let’s start as we always do with C++:  with a header file definition:

   1: #pragma once
   2: #include <map>
   3: #include <vector>
   4: #include <string>
   5:  
   6: using namespace std;
   7:  
   8: namespace BusinessRecommendations
   9: {
  10:     class RecommendationTracker
  11:     {
  12:     public:
  13:         typedef vector<pair<string, unsigned int>> RecommendationTotalsList;
  14:  
  15:         void AddRecommendation(const char* recommender,
  16:                                const char* business);
  17:         void GetRecommendationTotals(RecommendationTotalsList& recommendations) const;
  18:  
  19:     private:
  20:         typedef map<string, vector<string>> RecommendationsMap;
  21:  
  22:         RecommendationsMap recommendations;
  23:  
  24:         bool HasRecommenderRecommendedBusiness(const char* recommender,
  25:                                                const char* business);
  26:         void RecordRecommendation(const char* business, const char* recommender);
  27:     };
  28: }

This will define the basic contract that consuming code will use to work with the RecommendationTracker class.  Pay particular attention to the usage of pointers, references, “constness”, and other statements used to tell the compiler what to do.

Now that we’ve declared the contract, lets look at the overly verbose definition of the class:

   1: #include <algorithm>
   2: #include "RecommendationTracker.h"
   3:  
   4: using namespace std;
   5:  
   6: namespace BusinessRecommendations
   7: {
   8:     void RecommendationTracker::AddRecommendation(const char* recommender,
   9:                                                   const char* business)
  10:     {
  11:         if (HasRecommenderRecommendedBusiness(recommender, business))
  12:             throw exception("You may only recommend the same business once.");
  13:  
  14:         RecordRecommendation(business, recommender);
  15:     }
  16:  
  17:     void RecommendationTracker::GetRecommendationTotals(
  18:         RecommendationTotalsList& recommendationTotals) const
  19:     {
  20:         for (RecommendationsMap::const_iterator it =
  21:             recommendations.begin();
  22:             it != recommendations.end();
  23:             ++it)
  24:         {
  25:             recommendationTotals.push_back(make_pair(it->first,
  26:                 it->second.size()));
  27:         }
  28:     }
  29:  
  30:     bool RecommendationTracker::HasRecommenderRecommendedBusiness(
  31:         const char* recommender,
  32:         const char* business)
  33:     {
  34:         RecommendationsMap::const_iterator it =
  35:             recommendations.find(business);
  36:         if (it == recommendations.end())
  37:             return false;
  38:  
  39:         return find(it->second.begin(), it->second.end(), recommender) !=
  40:             it->second.end();
  41:     }
  42:  
  43:     void RecommendationTracker::RecordRecommendation(const char* business,
  44:                                                      const char* recommender)
  45:     {
  46:         recommendations[business].push_back(recommender);
  47:     }
  48:  }

Once again, we have several statements regarding constant state of pointers, language like “it->second.size()”, which refers to the size of a vector that is the value portion of a map.  Look at how the class name is repeated before each function definition; this tells the compiler what scope we are in.

Although not appearing in this example, frequently C++ programmers have to tell the compiler whether they want to allocate objects’ memory on the stack or heap, and have to manage the lifetime of that memory.

All of these intrinsic C++ concepts force the programmer to be very technical.  Much time is spent on considering the language itself rather than the domain.

The C# Definition (a little better)

Now lets port this same class to C#.  C# is a bit more modern than C++, slightly more functional, and isn’t so concerned about telling the compiler what to do.

One slight problem:  since there is no easy way to return a list of pairs as in the C++ definition, I’ve created a small helper class called “RecommendationTotal.”  I could have returned a dictionary, but the usage of a dictionary in C# usually suggests something other than name/value pairs:

   1: namespace BusinessRecommendations
   2: {
   3:     public class RecommendationTotal
   4:     {
   5:         public RecommendationTotal(string business, uint numberOfRecommendations)
   6:         {
   7:             Business = business;
   8:             NumberOfRecommendations = numberOfRecommendations;
   9:         }
  10:  
  11:         public string Business
  12:         {
  13:             get;
  14:             private set;
  15:         }
  16:  
  17:         public uint NumberOfRecommendations
  18:         {
  19:             get;
  20:             private set;
  21:         }
  22:     }
  23: }

Some may argue that this adds readability to the code; for C#, it probably does.  Now, the definition of the RecommendationTracker class is as follows:

   1: using System;
   2: using System.Collections.Generic;
   3:  
   4: namespace BusinessRecommendations
   5: {
   6:     public class RecommendationTracker
   7:     {
   8:         private readonly IDictionary<string, ICollection<string>>
   9:             recommendations = new Dictionary<string, ICollection<string>>();
  10:  
  11:         public void AddRecommendation(string recommender, string business)
  12:         {
  13:             if (HasRecommenderRecommendedBusiness(recommender, business))
  14:                 throw new Exception(
  15:                 "You may only recommend the same business once.");
  16:  
  17:             RecordRecommendation(business, recommender);
  18:         }
  19:  
  20:         public IEnumerable<RecommendationTotal> RecommendationTotals
  21:         {
  22:             get
  23:             {
  24:                 foreach (var business in recommendations.Keys)
  25:                 {
  26:                     yield return new RecommendationTotal(business,
  27:                         (uint) recommendations[business].Count);
  28:                 }
  29:             }
  30:         }
  31:  
  32:         private bool HasRecommenderRecommendedBusiness(
  33:             string recommender, string business)
  34:         {
  35:             return recommendations.ContainsKey(business) &&
  36:                 recommendations[business].Contains(recommender);
  37:         }
  38:  
  39:         private void RecordRecommendation(string business,
  40:                                           string recommender)
  41:         {
  42:             if (!recommendations.ContainsKey(business))
  43:             {
  44:                 recommendations[business] = new List<string>();
  45:             }
  46:  
  47:             recommendations[business].Add(recommender);
  48:         }
  49:     }
  50: }

It’s nice that the declaration and definition are now combined into one, and we don’t have to deal with header files anymore.  However, there are still many references to compiler instructions.

Every method must tell the compiler whether it should make the method public or private.  There are several references to types, such as “IEnumerable<RecommendationTotal>” and “IDictionary<string, ICollection<string>>.”

These compiler instructions and type declarations take our minds away from thinking about the business and have us spend more time on thinking about programming.

The Ruby Definition

Now let’s look at the same code in Ruby:

  1: class RecommendationTracker
  2:     def add_recommendation(recommender, business)
  3:       has_recommender_recommended_business?(recommender, business) do
  4:         raise StandardError, 'You may only recommend the same business once.'
  5:       end
  6: 
  7:       @recommendations[business] << recommender
  8:     end
  9: 
 10:     def recommendation_totals
 11:       recommendation_totals = Hash.new
 12: 
 13:       @recommendations.each_key do |business|
 14:         recommendation_totals[business] =  @recommendations[business].length
 15:       end
 16: 
 17:       recommendation_totals
 18:     end
 19: 
 20:   private
 21:     def initialize
 22:       @recommendations = Hash.new do |recommendations, business|
 23:         recommendations[business] = Array.new
 24:       end
 25:     end
 26: 
 27:     def has_recommender_recommended_business?(recommender, business)
 28:       return false if !@recommendations.has_key? business
 29:       return false if !@recommendations[business].include?(recommender)
 30: 
 31:       yield
 32:       true
 33:     end
 34: end

Notice how concise the Ruby definition is?  Compiler instructions are kept to a minimum, and the statements seems to emphasize the logic of the domain more.  Furthermore, all unnecessary references to types have been removed.

Simple one line boolean statements have been rearranged in a “action if condition” format instead of “if action then condition.”  Methods have a question mark to add to readability.  Overall, the code is simply a lot more expressive.

Summary

Contrast the Ruby definition to the original C++ definition.  We’ve come a long way haven’t we?  What’s frightening is to think that Ruby was created in 1992!  For some reason, it’s only recent that it has been receiving a lot of attention (probably due to Rails and the direction of software toward web platforms).

C++ is still a great language, and has its place within specific industries.  Game development is still primarily performed in C++, mainly due to the performance requirements where every bit of speed must be squeezed out of the code.

C++ is definitely a professional programming language, and requires the programmer to understand the power it can provide.  A high degree of technical aptitude is required to create concrete, easy to understand, scalable, and powerful applications in C++.  Unfortunately, it also takes more time.

However, instead of focusing on technical aptitude, developers should focus on adding value to the domain they are developing for.  This means most thought should be spent thinking about how best to express the core domain, where all of the business rules and logic should reside.

Compiler instructions and nifty technical tricks are cool, but they take time away from adding to the core domain.  As Eric Evans has illustrated, the easiest way to determine the core domain is ask why is the development effort important?  How will the code you are writing help the business compete?

The answer to those questions should point you straight to the core domain.  Expressiveness allows developers to define the core domain with a refined eloquence that helps lead to a deeper understanding of the problems at hand.

If source code is less infected with technical concepts, true domain problems are more easily read.  This greatly adds to maintainability, and allows object-oriented principles to be practiced more easily.

I am excited to see more developers diving into Ruby.  Hopefully, as the community becomes even more established than it already is, we will all start creating more elegant designs and expressions of problems without focusing too much on telling the compiler what to do.

  1. gravatar

    # by Anonymous - June 8, 2009 at 6:57 AM

    Try Scala

  2. gravatar

    # by Anonymous - June 8, 2009 at 11:12 PM

    Ruby is dynamically typed. What you supposedly gain in expressiveness is immediately lost because of the dynamic typing - no good tooling (refactoring, intellisense).

    Take a look a Boo (http://boo.codehaus.org) - the best of both worlds.

    Ruby is the past. I'm looking to the future where you can eat your cake and eat it too.

  3. gravatar

    # by Anonymous - June 9, 2009 at 6:37 AM

    http://www.jetbrains.com/ruby/index.html

  4. gravatar

    # by Anonymous - July 8, 2009 at 9:52 AM

    That girl in the picture is probably a 10110