Elements of SQR Style

Elements of Style is the famous book by William Strunk, Jr. and E.B. White.  First written in 1918, it is a guide to writing well in American English.  Anything good about the prose on this blog should be credited to Strunk & White.  The flaws must be ascribed to me.  This week’s blog entry is dedicated to that short book.

Fortran Versus Cobol

I learned Basic, Assembly, Fortran, and Cobol in school, and I did far more development in Basic and Fortran than Assembly and Cobol.  To me, it seemed easier and simpler to perform mathematical calculations with formulae (my spell checker objects to the Latin form), than to sequence each operation through a series of temporary variables.  Consider the solutions to a quadratic equation.

let #x1 = (-#b + sqrt(#b*#b – 4*#a*#c)) / (2*#a)
let #x2 = (-#b - sqrt(#b*#b – 4*#a*#c)) / (2*#a)

versus

move #b to #temp1
multiply #b times #temp1
move 4 to #temp2
multiply #a times #temp2
multiply #c times #temp2
subtract #temp2 from #temp1
do square_root(#temp1, #x1)
move #x1 to #x2
subtract #b from #x1
divide 2 into #x1
divide #a into #x1
add #b to #x2
multiply -1 times #x2
divide 2 into #x2
divide #a into #x2

procedure square_root(#input, :#divisor)
move 1 to #divisor
while {true}
  move #input to #dividend
  divide #divisor into #dividend
  add #divisor to #dividend
  divide 2 into #dividend
  subtract #dividend from #divisor
  if #divisor > -.0001 and #divisor < .0001
    break
  end-if
  move #dividend to #divisor
end-while
end-procedure

Did you recognize Isaac Newton’s method for square roots?

When I started programming in SQR, I adopted the let command and avoided the commands that reminded me of Assembly or Cobol; add, subtract, multiply, divide, and move.  Even reading about the performance advantages of those commands couldn’t persuade me to use them.

Now I use them to communicate the intentions behind my arithmetic.

The Subtext Of A Command

Psychologists say that most of what we communicate is non-verbal; our tone of voice, the cadence of our words, our facial expressions, and our body language.  There is some of that in our programs; vertical spacing, horizontal spacing, and how we divide a command into multiple lines.

I would argue that our choice of commands can tell another programmer more than it tells the SQR compiler, if we make the choices consciously and consistently.  Consider the characteristics of the let command versus the others (add, subtract, multiply, divide, and move).

  • The let command is followed immediately by – and highlights – a variable whose value is about to change.  The other commands are followed immediately by – and highlight – a variable or literal whose value will stay the same.
  • The variable after the let command can be a date, number, or string variable.  The variable or literal after the move command can also be a date, number, or string.  However, the variable or literal after the other commands is constrained by the command to be numeric.  There are other data manipulations commands (concat, encode, extract, find, lowercase, uppercase, string, and unstring) that constrain their values to be strings.  These commands emphasize the nature of the data.
  • After the equals sign, the let command allows us to write complex formulae that could be the equivalent of many, many single operation commands.  This suggests that there is always some potential for complexity, even if the formula happens to be simple.  After the preposition keyword (to, from, times, into), the other commands limit us to a single variable.
  • The let command says “anything can happen.”  Perhaps #x equals #x + 1 today, but we might change the program to set #x to 3.14159 * #r * #r tomorrow.  If we make it our practice, the other commands can suggest stability in our algorithms.
  • The let command can replace any of the other commands we’ve mentioned, and in other languages it can or must be omitted.  It doesn’t really tell us or highlight anything about the calculation to follow.  The other commands define the calculation and limit the syntax of the rest of the command.

Initializing Variables

Initializing a variable is just one example of setting its value.  The compiler doesn’t treat the command any differently, but it has a special role in our algorithms.  We can differentiate initialization by using the move command and substitution variables rather than the let command.  I try to initialize all my variables in my “start_program” procedure, in alphabetical order.  Here are some approaches.

#define initialize      get
#define to-zero         from initzero(0)
#define to-null         from initnull(0)
#define initialize-#    move 0 to #
#define initialize-$    move '' to $

begin-procedure create_arrays  ! this can be in an include file
create-array name=initzero size=1
  field=zero0:integer=0
  field=zero1:integer=0
  field=zero2:integer=0
  field=zero3:integer=0
  field=zero4:integer=0
  field=zero5:integer=0
  field=zero6:integer=0
  field=zero7:integer=0
  field=zero8:integer=0
  field=zero9:integer=0

create-array name=initnull size=1
  field=null0:char=''
  field=null1:char=''
  field=null2:char=''
  field=null3:char=''
  field=null4:char=''
  field=null5:char=''
  field=null6:char=''
  field=null7:char=''
  field=null8:char=''
  field=null9:char=''
end-procedure create_arrays

begin-setup
declare-variable
  date $third_millenium
end-declare
end-setup

begin-program
{initialize} #a #b #c #d #e {to-zero}
{initialize} $a $b $c $d $e {to-null}

move '01-JAN-2001' to $third_millenium date

{initialize-$}y  ! no space between curley bracket and variable
{initialize-#}z
end-program

We don’t have to include the # and $ symbols in the substitution variables, but we need one for numbers and one for strings. I thought the easiest way to name them was to use the symbols, and if I was using the symbols, I didn’t need to use the symbols again on the variables.

Incrementing Variables

Adding one to a number is such a common and special operation that C gave us an operator that did nothing but that; x++ or ++x adds one to the x variable.  Then, people named a language after that operator; C++, pronounced “cee plus plus.”

We add one to variables to count business objects (e.g. number of employees, number of customers) and to control our program flow (e.g. number of loop iterations).  This is conceptually different from an operation like “let #y = #x + #a” where #a just happens to be equal to 1.  It’s even different from an operation like “let #y = #x + 1” where #y is intended to be a value slightly greater than #x.

Here are some ways we can call attention to the fact that we’re not just doing addition, we are counting.

#define count-#    add 1 to
#define next-#     add 1 to

{initialize-#}i
while #i < #number_of_i
  if emp.deptid(#i) = ‘1234’
    {count-#}dept1234_employees
  end-if
  {next-#}i
end-while

Note that the substitution for {count-#} is the same code as for {next-#}, but count and next mean something different to the reader.

Summing

I don’t like to use the add, subtract, multiply, or divide commands when they make me a liar, no matter how briefly.  Consider these cases.

  1. move #foreign_revenue to #global_revenue
  2. add #domestic_revenue to #global_revenue
  3. subtract #expenses from #revenue
  4. move #revenue to #profits

At the end of the first command we have a value in #global_revenue that isn’t global revenue.  If we put a print statement or a subroutine call between 1 and 2, we could use #global_revenue prematurely.  If we perform the math and then the move, as with 3 and 4, we have the same problem.  We can avoid this issue with the let command.

let #global_revenue = #foreign_revenue + #domestic_revenue
let #profits = #revenue - #expenses

You may disagree, and leave a comment if you do, but I feel these cases are different from the one below.

{initialize-#}i
{loop-with-#}i
  get #amount from array(#i) amount
  add #amount to #total
  {next-#}i
{end-loop}

True, #total is not the total of the array until the last addition.  However, it is the total of the amounts so far at every point.  Also, loops suggest that any variables within a loop may have interim values.  Finally, it’s not possible to unroll every loop into a single let command, especially those with an unpredictable number of iterations.  On the other hand, the previous examples are easy to clarify with let commands.

Subtract, Multiply, and Divide

I’ve done most of my “subtexting” with move and add.  In my work experience, I haven’t had much opportunity to use subtract, multiply, and divide without feeling like a liar (see above).

I decremented loop counters in assembly language in order to use the “compare to zero” opcodes, but there’s no need to do that in a higher level language.  I decremented an array index in my binary/linear search algorithm, but I only wrote that routine once.

I don’t want to “multiply #pay_rate times #hours” because the #hours variable shouldn’t contain gross pay, even for one line of the program.  I haven’t had any assignments to calculate factorials or loan balances:

move 1 to #factorial
move 0 to #i
while #i <= #number
  multiply #i times #factorial
  add 1 to #i
end-while

move 0 to #y
while #y < #num_years
  multiply 1.05 times #principal
  add 1 to #y
end-while

The divide command has built-in “divide by zero” error handling, but I haven’t found a time to use it without “lying.”  Has anyone else?

One Comment

  1. Bob Josephson says:

    I disagree.

    Thus I leave a comment.

    But seriously… your example of looping to calculate a total is OK if there’s no way to exit the loop prematurely (just as calculating global_revenue is safe as long as no bozo sticks a line of code in the middle). But what if you leave the loop early due to an ON ERROR statement?

    The real solution is to have a temporary variable (e.g. accumulate_total) in the loop that is discarded after the calculation.

    I freely admit that I’m frequently too lazy to follow my own advice.

    - Bob