Fabulous Adventures In Coding

Eric Lippert's Blog

Translating intentions and mechanisms

Before I get into today's blogging, a quick note about my recent post on How To Not Get A Question Answered. That was certainly not intended to be fishing for compliments or chiding people for never acknowledging help ten years ago; that said, I appreciate both. Thanks to everyone who made thoughtful comments.

Moving on; a theme that seems to be coming up over and over again in my recent technical conversations is that of intention vs. mechanism. We have tried hard in C# 3.0 to make a language where there is a good balance between the code reading as a declaration of what meaning you intend the code to represent, and reading as a list of imperative instructions specifying a mechanism which achieves those intentions. I've been accumulating anecdotes about this tension between representing intentions vs. mechanisms; expect this to be a recurring theme in the blog for the next while.

Here's a question I got recently which speaks to this tension with regards to the subject of porting code from one language to another:

I have some C++ code with a macro in it. A typical usage looks like this:

TRY_WAIT_OP(Execute());

This macro expands to code similar to this:

for(int i=0; i<10; i++) {
    if (Execute()) break;
    Sleep(i*1000);
}

We are translating this code into C#, but C# does not have a #define directive. How do I do textual replacement of code in C#?

Once more, we have someone looking for a thin metal ruler. There's a problem -- represent the meaning of a common operation in a programming language. C++ provides a solution mechanism -- using preprocessor-based metaprogramming to create a nonstandard control flow primitive. The natural tendency when doing a translation is to find the identical mechanism in the new language, and then translate the code to use that mechanism. But what if there is no such mechanism?

In that case, you've got to translate the intentions, which after all, is what you are trying to translate in the first place. Presumably the new code is intended to do the same thing as the old code. Translating the mechanisms is just a particularly easy road to translating the intentions. What is the meaning of this macro?

Clearly TRY_WAIT_OP means "execute some arbitrary code that returns a Boolean. If it returns true, you're done. If it returns false, wait some amount of time and try again, up to ten times".

Now think about how you would write code from scratch that implemented those intentions in C#. Don't think at all about how it was written in C++. Your goal here is to solve the same problem using a different tool, so don't use the same techniques that you used for the other tool if they're not appropriate. Use the techniques that are appropriate for this tool.

The way I would write that in C# is to write a method that takes as its argument some arbitrary code that returns a Boolean. "Arbitrary code" is represented in C# by a delegate. We can do a bit better than the macro while we're at it, and return a success code:

private static bool AttemptMultiple(Func<bool> action) {
    Debug.Assert(action != null);
    const int maxAttempts = 10;
    const int delay = 1000;
    for (int attempt = 0 ; attempt < maxAttempts ; ++attempt) {
        if (action()) return true;
        Sleep(attempt * delay);
    }
    return false;
}

Lambda expression syntax gives us a nice way to do the call:

AttemptMultiple(()=>Execute());

A code porting project is a good opportunity to review the design fundamentals:

  • Why 10 retrys?  Should this be a parameter to AttemptMultiple?
  • Why wait 1000 milliseconds instead of some other delay? Should this also be a parameter?
  • Why is the increasing delay linear rather than constant, geometric, etc? Should the delay strategy be a parameter?

The above questions are good but they rather miss the point. The important question is: is this functionality even a good idea in the first place?

This last point is key. The "try it, fail, wait, try again" strategy is in general a dangerous one because it does not compose well with itself. Consider the following:

bool SendPackets()   { ... if (!AttemptMultiple(()=>{ ... })) return false; ... }
bool TalkToSocket()  { ... if (!AttemptMultiple(()=>SendPackets())) return false; ... }
bool SendData()      { ... if (!AttemptMultiple(()=>TalkToSocket())) return false;... }
void HandleCommand() { ... if (command == SendData && !AttemptMultiple(()=>SendData())) ReportErrorToUser();... }

Now suppose that the user has a bad network card and SendPackets is always going to fail. If you look at any one of those lines, it looks like the attempt is being made ten times and will take a maximum of about one minute. In fact, the attempt to send the packet is made ten thousand times and will not report the error to the user for about a week.

Usually the right thing to do when something fails is to go into a failure state immediately. Tell the user that something failed and let them decide when and if to retry it.

What are some examples of a poor mismatch between intentions and mechanisms that you guys have seen? I'm interested in stories about:

  • mechanisms that subtly did not implement the intentions of the programmer
  • situations where the intention of the code was completely obscured by the mechanisms. How did you make the code better reflect the intentions?
  • situations where the mechanism of the code was important, but obscured by unnecessary emphasis on representing the intention. What were the negative consequences of obscuring the mechanism behind some abstraction?

Thanks!

 

Published Tuesday, March 25, 2008 10:26 AM by Eric Lippert

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

KAE said:

>AttemptMultiple(()=>Execute());

Why not "AttemptMultiple(Execute);" ?

March 25, 2008 2:53 PM
 

Eric Lippert said:

That would work in this case. But what if the code inside the macro took an argument?

March 25, 2008 2:55 PM
 

Matthew Douglass-Riley said:

Relevant to the "automatic retry" discussion:

http://blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx

March 25, 2008 6:21 PM
 

Bob Denny said:

In my case, it's dealing with atronomical instruments that occasionally misbehave in an automated image acquisition system. The devices operate below a standardized driver layer. And the real problem is political - users want me to do automatic retries instead of them having to fix the problem (or have their instrument supplier fix it!). It's very difficult to explain why I refuse to put in "recovery" code for their flaky device. It needs to be in the driver if anywhere, and I say "nowhere"; talk to the people to whom you paid money for the instrument. Tell them  to make it work reliably.

Anyway, the reasons for avoiding retries are (1) avoiding damage to equipment, and (2) avoiding bloating my software with device-dependent code, and simultaneously defeating the whole purpose of drivers. "No software victories over hardware!"

March 25, 2008 9:10 PM
 

Christopher Bennage said:

Great post, Eric. Keepem' coming.

March 26, 2008 12:21 AM
 

Olmo said:

The whole standard SQL synthax looks like an example of the third case (too much emphasis in intention). I think the Linq synthax is better, not only for allowing intellisense but for expressing the mechanism (order) the operations should be done.

When will we have a Microsoft Linq Server that takes ExpressionTrees insead of plain SQL :P

March 26, 2008 4:38 AM
 

RichB said:

Deadlocks is about the only area I've come across where this technique is useful.

March 27, 2008 9:39 AM
 

Merit said:

I imagine everyone here already knows about it but www.TheDailyWTF.com is chock FULL of examples of mismatches between intentions and mechanisms.

March 28, 2008 5:37 PM
 

WaterBreath said:

>> Why not "AttemptMultiple(Execute);"

> That would work in this case. But what if the code inside the macro took an argument?

There is a subtle lesson to be learned here about crafting examples, and possibly also about the illuminating aspect of teaching the general solution as opposed to the specific solution...  Thank you Eric, for an unexpected epiphany. =)

March 29, 2008 1:03 AM
 

Charlie Calvert's Community Blog said:

LINQ in Portuguese ( Direct ) http://www.linqpad.net Eric Lippert Why Can't I Access A Protected Member

March 30, 2008 8:45 PM
 

Charlie Calvert's Community Blog said:

Welcome to the forty-second issue of Community Convergence. The last few weeks have been a busy time

March 31, 2008 12:56 AM
 

Luc De Wilde said:

Hey Eric (and the others), I' m a Belgian banker who programs for fun (over ten years ago I did this for my job).  i surely agree with the translating manner, but I have some doubt with the statement: "Tell the user that something failed and let them decide when and if to retry it."  I explain: on my home-network we have now and then some transmission problems.  I have written a small program to make a website with photos en I have to transfer this websites to my webspace. The transfer of a website takes in certain cases an hour or more, so I want my computer to upload the website unattended .... but I did NOT find a (free) FTP-program that I could tell to retry for about a minute or two when something goes wrong (they all have read your statement?).  So I have written a simple FTP-program myself with the AttemptMultiple-approach ... and it works fine!     (Sorry for the errors in my writing English, my mother-language is Dutch.)

April 17, 2008 3:56 PM

Leave a Comment

(required) 
(optional)
(required) 
Submit

About Eric Lippert

Eric Lippert is a senior developer on the Microsoft C# compiler team. Before that he worked on the framework of Visual Studio Tools For Office. Before that, he worked on the compilers, runtimes and tools for VBScript, JScript, Windows Script Host and other Microsoft Scripting technologies. He lives in Seattle and spends his free time editing books about programming languages, playing the piano, and trying to keep his tiny sailboat upright in Puget Sound.

This Blog

Syndication


© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker