Genii Weblog

Using @Do as a expression

Tue 4 Jan 2005, 04:03 AM

by Ben Langhinrichs
There was one trick I learned when writing my Limits to Cleverness post that I thought might be worth expanding on.  In that post, I had a very long formula which essentially said

sorted := @Sort(Speaker; [CustomSort]; @If(@Do(first_stuff) > @Do(second_stuff); @True@False));

Now, leaving aside the specific intent, take a look at just the condition used in the @If

@Do(first_stuff) > @Do(second_stuff)

I have used @Do before on occasion, as it has been part of formula language for a long time, but I never thought about the fact that, like almost everything else in formula language, it evaluates to a value.  I have always just used it to group multiple statements.  But it does return a value.  To quote the Designer Help for @Do
Evaluates expressions from left to right, and returns the value of the last expression in the list.
But even in the Designer Help, they seem to forget this.  The examples they give don't use the returned value, and the separate example in the "Order of evaluation for formula language" which describes @Do shows this:

@Do function
@Do executes a number of statements in sequence and can be used as an execution path within an @If function:
@If(LogicalValue; @Do(TrueStatement1; TrueStatement2); FalseStatement)
which misses the fact that it could equally well be 

@If(@Do(Statement1; Statement2; LogicalValue); TrueStatement; FalseStatement)

or, as I used it before

@If(@Do(Statement1; LogicalValue1) > @Do(Statement2; LogicalValue2); TrueStatement; FalseStatement)

which might sometimes be more compelling, but never seems to be suggested in the Help documentation.

Using temporary values inside @Do
There are many reasons to use @Do for its return value, but the most useful I have encountered since discovering this are the ability to setup temporary values and then use them, and the ability to convert the single expression in newer functions such as @Transform, @Sort and @For.  For those of you who only want to learn ND6 tricks, this latter use will be particularly interesting.

Let's take a very simple example, even though by virtue of its simplicity it is not a good use of the technique.  Let's assume you wanted to do an @DbLookup that returned a number list, and returned the sum of the numbers:

sum := @Sum(@DbLookup(""; ""; "PartsLookup"; part; 2));

Any good developer might cringe at this construction, because it doesn't handle errors.  A traditional way to handle this might be:

ret := @DbLookup(""; ""; "PartsLookup"; part; 2);
sum := @If(@IsError(ret); 0; @Sum(ret));

but another option is: 

sum := @Sum(@Do(ret := @DbLookup(""; ""; "PartsLookup"; part; 2); @If(@IsError(ret); 0; ret));

It isn't obviously better in any way, although it is interesting.  You would be better off with the first construction.  So, let's take a somewhat more complex, but better, example of an @For loop that checks each of the items in a list until it finds one that isn't in a lookup view or that has an obsolete product number (starts with "x").

pArray := "Rosebud Sled":"Holy Grail":"Tickle Me Elmo";
L := 1;
U := @Elements(pArray);

@For(i:=L;  i<=U & @Do(pNum := @DbLookup(""; ""; "(PartNums)"; pArray[i]; 2); @If(@IsError(pNum); @False@Begins(pNum; "x"; @False@True));  i += 1;
           @StatusBar(partsArray[i] & " exists!"));

Now, this would be a lot harder without @Do.  You could repeat the @DbLookup twice, once to check for an error, and the second time to check for the leading "x", but it is a lot simpler and more efficient to set the value once and check for an error and then check the value.  You could move all of this lookup and termination logic into the loop, but it would make the logic more verbose and awkward, since the termination condition is meant to be where it is.
Phew!  This post is probably long enough for now.  I'll tackle using @Do in other functions more in another post.

Copyright 2005 Genii Software Ltd.

What has been said:

261.1. Rob McDonagh
(01/04/2005 06:00 AM)

Cool, very cool.

But what is @LBound? Is that new in N/D 7 beta? Assuming it is what it looks like, in any event, is this a typo: wouldn't U be set to use a similar @UBound function - "@UBound(pArray);"?

261.2. Ben Langhinrichs
(01/04/2005 06:32 AM)

Oops. I was typing late at night and substituted this without testing. I just fixed it.

261.3. Richard Schwartz
(01/04/2005 09:34 PM)

@IfError, which was new in ND6, takes care of the @Sum example in a single expression without the need for the @Do


261.4. Ben Langhinrichs
(01/04/2005 11:01 PM)

@Rich - True, although it doesn't really help the issue with saving the result and then doing a further lookup or test on the return value. Still, thanks for pointing it out, as it is a better solution than the one I had.

261.5. Feri
(05/12/2006 01:10 AM)

clever but not perfect:

sorted := @Sort(Speaker; [CustomSort]; @Do(first_stuff) > @Do(second_stuff));

why do people use IF cond = true THEN?