Idiomatic Programming

Mark Grosberg http://www.conman.org/projects/essays/idioms.html

Language is probably human kind's most important invention. Itgoverns how we think and what we can think about. The same is truefor programming languages. The language we program in determines alot about the overall design of our programs.

Most programmers are really passengers. The language drives theirthinking instead of the other way arround. This is especially truefor the imperative family of languages. For those unfamiliar withthe term, an imperative language is based on things such asvariables, loops, conditionals, evaluation, and procedures.Basically, the majority of programmers use imperative languages ofsome sort and never experience anything else.

Thankfully, imperative languages are not the only way toconstruct programs. Some languages provide very limitedfunctionality, freeing programers from having to make so manychoices. This is one of the main driving forces behind functionallanguages. Some functional languages attempt to drive a programmersthinking towards the right direction. The problem with this is thatnot every problem is solved with the same mentality. Other languages(not all of which are necessarily functional) are infinitelyextensible (C++ does not count here, sorry).

Extensible languages allow the programmer to create a specialpurpose language oritented towards solving a particular problem. Forexample, given a task like expert system construction, a FORTHprogrammer might create a new word that compiles the expert systemfrom an easier to write format that would be possible with astack-based language.

Programmers who do this are telling the machine how to solve thatclass of problems instead of solving that one particular problem forthe computer. This is a far more efficient (both in terms ofprogrammer time and computational efficiency [if done right])approach. Essentially, you should always “make the computerspeak the language of the problem and not translate the problem intothe language of the computer.”


Okay, great. I have convinced you that you are programming withblinders on because of your programming language (or environment forthat matter). Changing programming languages can be an expensive,frustrating, and bug-inducing experience.

But you don't have to! In fact, most languages(such as C and Pascal+M4) have some sort of macro capability.Typically this capability isprovided by a preprocessor doing textualsubstitution. This means that macros do not quite integrateseamlessly with the language. But you live with what you've got.

While using macros can be dangerous (especially if you use themfor the wrong purpose) they can also be a wonderful structuringtool. I tend to use macros for three main purposes:

  1. Enforcing consistency withincomplex code
  2. Changing the syntax of my programming language (which happens to be ANSI C) to match my problem
  3. Automatically coding something that would be tedious anderror prone otherwise

I never use macros for the following reasons though:

  • Eliminating typing
  • Cut-and-copy style programming (hint: Rethink theproblem, there is probably a better way)
  • Masking what I am really doing

The one exception to this is that (in C) I use macros for“open coding” (inlining) functions. This is essentially anefficiency concern that I solve using macros because the C languagedoes not support inlining (sadly, but the compile-link mechanics ofmost C compilers prevent this).


Example 1: Enforcing consistency

When working on a large C++telecommunication system (that was designed from the start toexpand) our conventions call for every variable being accessed onlythrough “Get” and “Set” methods. This is becausein the future, simply updating a variable may require someassociated action to take place. Along the same lines, getting thevalue of a variable may require that some associated action takeplace.

Furthermore, because of the approach we took to writing the“Get” and “Set” methods variable manipulationlooks identical to function calling. Now instead of having toremeber two different concepts (assignment and function calling) youonly have to apply one. Consistantly.

To perform this feat, our group used a macro that generated allof the repetitive code necessary to implement the “Get”and “Set” methods. This macro looked like this:

#define property(name, type)   :                             \
                                type m##name;                \
                                public:                      \
                                 type name()                 \
                                    { return m##name; }      \
                                 void name(type t__##name)   \
                                    { m##name = t__##name; }     

Although I don't like the overloading feature of C++ it is ratheruseful in this case. The way this macro works is it declares thevariable with a lower-case m prefix (this was also requiredby our coding conventions). The macro also generates two accessmethods to get and set the value. The access methods have the samename as the variable. The way this macro can be used would be asfollows:

class MyClass
{
  protected property(age, unsigned short);
  private   property(gpa, float);

  private:  // Other stuff can go here
};

Users of this class can use the properties like this:

...
MyClass  someInstance;

someInstance.age(10);                    // Set the age
printf("This %hu-old has a %f GPA.\n",
       someInstance.age(),               // Get the age
       someInstance.gpa());              // Get the GPA
...

Notice that now there is no thought (or distracting need to lookup) what members of that class are functions and what members arevariables. For all intensive purposes they are the same.Consistantly. And the code to make them such (which was once typedin by hand each time) is automatcally generated. This removes agreat potential for bugs slipping in via typing errors.

Example 2: Changing the syntax

Some problems are just better solved using aspecial-purpose programming language. Any programmer who has everwritten a compiler both by hand and with tools such asLex and YACC knows that the automatictools can make a compiler faster (in the case of Lex)and easier to write and modify (in the case ofYACC).

Sadly, I am very reluctant to use Lex andYACC. Both of these tools are rather limited in theirinput mechanisms. As an added blow, the code generated by thesetools is not re-enterant. Making anything other than the traditionalcompile file 1 completely, compile file 2 completely,… structure of a compiler difficult to implement.

These were exactly the problems I faced when coding the front endfor my AMC compiler. In fact, the way AMC worked it completelyprevented me from using Lex and YACC. Myalternatives were to either:

  • Find another compiler-compiler that meets my requirements

or

  • Hand code the parser & lexer for AMC

Oddly enough I chose the latter. This turned out to be a verygood design decision. Not only is my parser easily modified and fastbut it is more robust than most because the macros I useautomatically use a scheme to take careof reclaiming resources in the event of failure. Without me evenhaving to think about it (because I sometimes forget, but thecomputer doesn't).

What I came up with was a set of macros that implemented somecommon actions such as:

  • A required token
  • A requried non-terminal
  • An optional token
  • An optional non-terminal
  • 1 or more repetitions
  • 0 or more repetitions
  • Selection
  • Declaring a non-terminal

These macros generate code that calls the parse/lexer runtime andmanages the allocations that they make (automatically). The neteffect is that if a syntax error occurs all of the resourcesallocated for the entire parse are automatically cleaned up and thestack is un-wound. What would have been very difficult to writebecame a few, very clear lines.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章