Skip to content

Using APL and J in Conjunction to Improve System Validity

Vector 14:3, page 91

development practice APL in perspective

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 91

Programming languages are usually seen in terms of opposition or contrast to one another: FORTRAN vs. Pascal, COBOL vs. PL/I, APL vs. J. Yes, definitely APL versus J. Vector and the Internet comp.lang.apl site are peppered with comparisons and contrasts as well as outright brickbats regarding the two languages (or is it dialects — I hope this paragraph does not start yet another holy war before I have even gotten to my main point).

I contend that there can be situations where APL and J can be used in a cooperative manner where the goal is maximising software system validity. This article is intended to explain my case.

Validating Software Systems

Give me some slack here: I’m a demographer, not a trained computer scientist. But I do write a lot of software to generate data to sell to clients, and I get bitten by software errors more often than I like. No doubt many of you are in a similar situation.

What is a ‘valid’ software system? For this article, I define it as one that does what the system designer intended. To take a simple case, a valid system would have no array indexing errors. I remember an instance where I got males and females reversed in one equation — just a matter of typing a 1 instead of a 2 by mistake. The system did not crash, so there was no obvious sign of trouble. The output looked a little odd, but nothing clearly unreasonable. I discovered the error months later when going over the code for some reason.

Is there any way to guarantee system validity? I say no, though there might be a few who disagree. About the best one can do is implement a practical validation procedure which reasonable observers agree is likely to locate a good many errors. If the system has any size or complexity, errors are almost certain to occur no matter what precautions are taken. Eventually, some of these will reveal themselves to users of the system or its output. Other errors (presumably small ones) might never be discovered.

Validation procedures can include code review, graphing output, and flagging results that might be considered questionable. There are others. But, in my opinion, there is only one best way. Read on.

The Solution is (Gasp!) Writing It Twice

Please calm down. I think I am quite reasonable when I assert that the best way to validate software systems is to write two systems that use the same input and compare the output. Admittedly, this is not always practical. The balance of this article sketches ways this can be done, given certain conditions. In brief, I propose that code be written in both APL and J, and I further claim that it is the use of these languages that makes the task practical at all. (Okay, I called them separate languages again. Please bear with me, because I will do so for the balance of the article.)

Where did I get the notion that writing a system twice is a practical proposition? Well, if you read the last paragraph of my article about the SAS IML matrix language in the July, 1997 Vector, you will note that I mentioned writing a system in J as well as IML. Because of my unfamiliarity with IML and SAS, as well as the lack of anyone to give me advice on how to build IML systems, my strategy was to restrict the IML system to core number-crunching chores. A good deal of input (rates, parameters, etc.) was computed off-line under J, but could have been done using a spreadsheet. Thus, the core system was small — about a dozen pages of code in small type. Partly because I didn’t trust my IML skills and partly because I wanted a backup system in a language I knew, I wrote a J system that used the same input files as the IML system and which produced the same format of output files. (The files are *.txt type — the file you get when you save an Excel spreadsheet as text: basic ASCII with tab delimiters.)

One result of my efforts was that it was now possible to compare output of the two systems number-by-number. So I did compare numbers, and where there were differences, I checked both systems to try to figure out which one was in error. There must have been six or eight cases where differences were found, and the errors were fairly evenly balanced between IML and J. As I write this, the numbers agree, so I am confident that the system has been validated. Yes, there might be design mistakes, and there might be input errors, but the systems themselves are highly likely to be doing what they are supposed to be doing.

Where This Sort of Validation is Practical

Let me move from a specific case to discuss dual-language system validation in terms of APL and J, rather than J and IML.

I contend that J can be used to validate APL systems and APL can be used to validate J systems. It is also possible to validate APL by APL and J by J, but this assumes different programmers for each version. Where there is only one programmer, two languages should be used to limit the possibility of code being subconsciously repeated. Also, a one-language, one-programmer rewrite might easily include the use of common utility code, which might itself be flawed. (The IML/J exercise mentioned above revealed one error in my J utility code toolkit.)

To me, APL and J are well suited to dual-programming. They have enough similarities that a programmer of one language does not have to experience a paradigm shift to learn the other. But the differences are great enough that the programmer has to think in different terms to code the same process in each language. I have been programming in APL since 1983, and have become so proficient that a good many of my coding errors are the result of writing too quickly — slapdash stuff. I am far more cautious when writing J. I’ve been using it fairly heavily for about a year, and have avoided hooks, forks, etc., because (1) I have yet to confront a situation where the potential gain seems to exceed the pain of learning them, and (2) I find that even basic, APL-like J is not easy. I have a general sense of frames and function rank, yet nevertheless find myself constantly testing code segments on simple arrays just to be sure I’ve gotten things right. And zero-origin counting is not what I grew up with, so indexing always takes thought and maybe even doodling a few numbers or diagrams on a scratchpad. Indeed, my mind is working quite differently when programming in one language or the other, and this is exactly what is needed for one-programmer system validation.

By the way, I ought to make it clear that J code should not include tacit functions: the APL and J ought to be in the form of conventional programs whose listings can be compared and the behaviour of each variable traced. In tacit programming, the variable ‘disappears’ so far as the code is concerned. And you can’t test the data flow of a tacit expression directly, if I understand the concept correctly.

One of the most important reasons why I think APL and J are good dual-programming tools is coding speed. APLers tend to throw a lot of speed factors around, especially lines of code compared to doing the same job in a compiled language. My impression is that the range runs from three to five times coding speed improvement. To use numbers loosely, assume APL and J can be written equally quickly by a programmer experienced in both. And take the more conservative end of the speed range. This implies that, compared to writing in a compiled language, the same system can be written in both APL and J in two-thirds the time. Or 40 per cent of the time, if the high-end speed advantage is accepted. This, coupled with the payoff of less time spent in the future tracking down system bugs, means that the array-language programmer still retains the development-time advantage over competitors using conventional languages!

Potential Problem Areas

Nothing in this world is perfect. There are several practical matters standing in the way of dual-language validation in APL and J. These need to be discussed, lest we get carried away by the prospect of theoretical gains.

Before dealing with problem areas, let us start by describing an ideal dual-language validity testing setting because, by showing an ideal, we can reveal what has been abstracted out of practical circumstances. Actually, the IML/J example mentioned above came pretty close; let’s use a polished version of it. Here are salient features:

  1. The core computational task is well-structured. There can be alternative subroutines, but most of the variation is in the input data and parameter values. Essentially, the core is a batch-process wrapped in some sort of interface.
  2. The identical input must be brought to each language system test run so that any differences in output must be due to system flaws.
  3. The simpler the user interface (UI) the better — at least for testing purposes. This is an enabling factor relating to the need to have each system running identical input. Since UI’s deal with input selection and flow of control, steps must be taken to assure common system control. The ideal setting would have no interface in the generally accepted sense; it would be a software ‘breadboard’ or workbench where consistent inputs are introduced as globals, passed data, or whatever is the design of the production system. In other words, for testing, all the data/parameter selecting normally done via the UI can be assumed to have occurred, and the core routines are now about to begin their work. For complex systems, modules might have to be isolated in this fashion for individual testing — particularly if the complete system requires UI actions to bridge module operations.
  4. Output data should be in the form of tables containing summary data, descriptive data such as rates or shares, and blocks of raw numbers. Summary and rate data are useful for spotting the general locations of errors, detailed data confirm that error fixes work at the lowest level. Demographic data I have used for testing contained single-year-age data as well as age groups, population totals, and totals for process outcomes such as births, deaths, and migrants.

From the above, there are some obvious and not-so-obvious practical problem areas. But the key item is how well the test systems can be isolated from UI and language environment peculiarities. Make every reasonable effort to create test-beds. Otherwise, you are likely to encounter the following problems:

One obvious problem is the UI. My experience does not extend to mainframe or minicomputer APL, but I know from experience differences between APL*PLUS and APL2/PC under MS-DOS, not to mention APL+WIN, Dyalog APL/W, ISIAPL, and J under Windows. The UI, whether it is basically a fancy menu or something far more flexible, can influence much data selection and program flow. The dual-language problem is how to get the same input into these separate code blocs. If the UI was via Delphi or Visual Basic, and the J and APL were DLLs chewing on data furnished by the UI system, there would be little difficulty. (Few APLs can act as DLL servers as this is written, but in future most will.) Problems can occur under Windows if you use a language vendor-based UI. For example, what if the target system was written in Dyalog APL/W and used the grid object? J under Windows does not yet have such a thing. A problem-simplifying Windows UI solution (though not for grid objects) would be Causeway — except that there is not yet a Causeway for J.

Another problem is data input — in particular, what can be done with target systems that make use of APL component files? J does not read APL component files. The same applies to data stored as part of an APL workspace. As a practical matter, dual-language validation systems are best suited to settings where data flow to and from ‘standard’ file types, be they *.txt or files read from data bases using ‘standard’ linkage software.

I know this is easy to say but hard to do in the real world, but: if I were developing new systems in APL and hoped to perform dual-language validation using J, I would try to design the production system (not just a breadboard system) so as to eliminate all aspects of APL data storage as it was classically practised. This includes the workspace as well as component files. Okay, you can’t run APL without a workspace. But, aside from loading-speed considerations, why should APL systems be stored as workspaces? The fear of destroying programs by mistake led me to abandon workspace-based systems soon after I started using APL. I store programs as individual files grouped within directories. It is a flexible and safe solution. It also helps facilitate dual-language validation because specific program sets can be assembled fairly easily for testing as functional blocs as noted above. If APL is to have a future, dysfunctional baggage must be ruthlessly abandoned where necessary.

Conclusion

Validating software systems by writing them in two different languages can be a practical matter if those languages are APL and J, provided that identical input can be brought to each comparative test. The key element in making this proposal practical is the programming speed of the two languages — writing a system in both APL and J to assure validity can be done faster than programming it once (without attempting validation) using a conventional language.