Q L H A C K E R ' S J O U R N A L =========================================== Supporting All QL Programmers =========================================== #25 July 1996 The QL Hacker's Journal (QHJ) is published by Tim Swenson as a service to the QL Community. The QHJ is freely distributable. Past issues are available on disk, via e-mail, or via the Anon-FTP server, garbo.uwasa.fi. The QHJ is always on the look out for article submissions. QL Hacker's Journal c/o Tim Swenson 5615 Botkins Rd Huber Heights, OH 45424 USA (513) 233-2178 swensontc@mail.serve.com http://www.serve.com/swensont/ EDITOR'S FORUMN I don't have much to say for an introduction to this issue. I do want to thank Peter Tillier for contributing two articles. He really filled a few pages for me. The more articles I get the easier it is on me and the more often I can publish. I hate it when I have programming dry spells. In QHJ #22, was a Day of the Week program. Mel Laverne found out one small bug in the program that did not make it work. Then translating from C to SuperBasic, I forgot that the original program was done with Integer arithmatic. SuperBasic defaults to floating point, so the program was off fairly often. So change all variables to integers and the whole thing should work out. While browing the Internet recently, I came across an article that I had heard about but had not read; The Tao of Programming. The Tao of Progamming is written in a very Eastern way of writing, with formal sounding wisdom, but sprinkled lightly with modern humor. Here is an example: "The Tao gave birth to machine language. Machine language gave birth to the Assembler. The Assembler gave birth to the compiler. Now their are ten thousand languages. Each language has its purpose, however humble. Each language exprresses the Yin and Yany of software. Each language has its place within the Tao. But do not program in COBOL if you can avoid it." If you find the Tao of Programming, give it a read. I hope you like this issue, and I'll see you on the 'Net. BOOT UP REMINDER Productivity tools for the QL are far and few between. On the PC, there is a dirth of these tools; Meeting Maker, Lotus Organizer, Maximizer, etc. One feature of most productivity tools is the ability to remind you of special days, such as birthdays, anniversaries, appoinments, and so on. Without doing much development work, a simple day reminder can be written for the QL. A good way to setup a reminder program is to have it check for special days when the QL boots up. During boot up, the program reads in the reminder data file and outputs any special days that are set for today. These special days can be set up to appear yearly (a birthday), weekly (trash day), or monthly (bills, bills, and more bills). Of course, this program will only work well if you boot up your QL at least once a day. If you boot it up less than that, you will need to set your reminders to appear days before the special day. The format of the reminder file ( reminder_dat ) is as follows: T:XXXXXX:..................... Where T is the type of reminder W for weekly, M for montly, and Y for yearly XXXXXX is the date of the reminder ...... is the text of the reminder Colons seperate each field. The program is case insensitive. There are three types of reminders, weekly, monthly, and yearly. A weekly reminder is based on the day of the week. If you must take out the trash every Wednesday night, then you could set a reminder for Wed to say "Take out Trash." The first field has a W and the second field has a three letter abreviation for the day of the week. Mon for Monday, Tue for tuesday, etc. This is all based on the format returned from DAY$. A Monthly reminder is based only on the day of the month. If you have to pay a bill on the 1st of each month, you could set a montly reminder to "Pay Bill" for the 1st. The first field has an M and the second field is the day of the month in a two number format. The 6th of the month would be listed as 06. A yearly reminder is based on the month and day. This is for reminding you of things like birthdays. The first field has a Y and the second field has a three letter abreviation for the month (Jun), a space, and the day of the month listed as two digits (06 for the 6th). The 4th of Jul would be listed as "Jul 04". The text of the reminder is the last field. It goes from the second colon to the end of the line. You can put anything in this text, as it is copied from the reminder file and printed to the screen. This program can easily be included into a Boot program or it can be called from the Boot program. It simply prints out the reminders, but you can liven it up with flashing letters or beeping noises, what ever will get your attention. 100 OPEN #3,scr_350x75a75x50 110 PAPER #3,0: INK #3,2: BORDER #3,3,4 120 CLS #3 130 month$ = DATE$ 140 month$=upper$(month$(6 TO 11)) 150 daym$ = DATE$ 160 daym$=upper$(daym$(10 TO 11)) 170 dayw$=upper$(DAY$) 180 OPEN_IN #4,flp1_reminder_dat 190 REPeat loop 200 IF EOF(#4) THEN EXIT loop 210 INPUT #4,in$ 220 IF LEN(in$) < 3 THEN END REPeat loop 230 colon = ":" INSTR in$ 240 type$ = upper$(in$(1 TO colon-1)) 250 in$ = in$(colon+1 TO ) 260 colon = ":" INSTR in$ 270 remind$ = upper$(in$(1 TO colon-1)) 280 reminder$ = in$(colon+1 TO ) 290 IF type$ = "W" THEN 300 IF remind$ = dayw$ THEN 310 BEEP 1000,10 320 PRINT #3,dayw$;" ";reminder$ 330 END IF 340 END IF 350 IF type$ = "M" THEN 360 IF remind$ = daym$ THEN 370 BEEP 1000,10 380 PRINT #3,daym$;" ";reminder$ 390 END IF 400 END IF 410 IF type$ = "Y" THEN 420 IF remind$ = month$ THEN 430 BEEP 1000,10 440 PRINT #3,month$;" ";reminder$ 450 END IF 460 END IF 470 END REPeat loop 480 CLOSE #4 490 CLOSE #3 500 DEFine FuNction upper$(up$) 510 LOCal x, temp 520 FOR x = 1 TO LEN(up$) 530 temp = CODE(up$(x)) 540 IF temp > 96 AND temp < 123 THEN up$(x)=CHR$(temp-32) 550 NEXT x 560 RETurn up$ 570 END DEFine upper$ Example Reminder File: w:tue:This is a Tuesday Reminder w:wed:This is a Wednesday Reminder m:04:This is a 4th day of the month Reminder m:13:This is a 13th day of the month reminder y:jun 04:This is a June 4th reminder y:jul 19:This is a July 19th reminder PARAMETER PASSING MECHANISMS By Peter Tillier There are many parameter passing mechanisms that are used to pass data into, out of or both into and out of procedures or functions being accessed. What is said here applies to procedures and functions in the Pascal sense and to functions in the C sense; so I'll omit 'or function' for the rest of the article. Mechanisms for Passing Data into Procedures. -------------------------------------------- There are basically three types of input passing mechanism:- - call by value; - call by constant-value; - call by reference-constant. Note that this list doesn't include call by reference - see later. Most languages support only call by value, although ADA, for example, supports call by constant-value and can support (implementation dependent) call by reference-constant. In call by value the actual parameter's value is passed as an initial value to the formal parameter at the time the procedure call is made and the formal parameter (total in the following example) then acts as a local variable within the called procedure (i.e., it can be modified within the procedure). So, procedure DoSomethingWith(total : integer); {in Pascal} var symbol : char; begin while total > 0 do begin read(symbol);write(symbol); total := total - 1 { total is a local variable in Pascal } { so this is OK } end end { DoSomethingWith }; In some versions of Pascal a formal parameter isn't allowed to be the controlling variable of a 'for' loop, so this wouldn't compile using the ProPascal compiler: program fortest; var z : integer; procedure test(j : integer); begin for j := j downto 0 do writeln( j ) { this line may fail to compile } { depending on the compiler } end { test }; begin { fortest } z := 10; test( z ) end { fortest }. [Note that, for example, Turbo Pascal (PC) does allow this sort of thing to compile if the formal parameter j is a value parameter (as shown) whereas it fails to compile if j is declared as a var parameter (which is very sensible!)] In call by constant-value, the formal parameter simply receives the value of the actual parameter and then acts as a constant within the procedure. In ADA this is implemented using the 'in' mode keyword (which is implied if no mode is given explicitly), e.g, procedure do_something_with(total: in integer) is { in ADA } symbol: character; count : integer := total; { required in ADA as total is constant } begin while count > 0 loop get(symbol); put(symbol); count := count - 1; { can't use total := total - 1 } end loop; end do_something_with; in this case 'total' is treated purely as a constant within the procedure and the variable count is needed to control the loop iterations. The main problem with call by value and call by constant-value is that when aggregates such as Pascal (or Modula-2) records are passed to procedures, then exact copies of the actual parameters need to be made and placed in the formal parameters, which is very expensive in both space and execution time. A common work-around in Pascal and Modula-2 is to pass the parameters using call by reference, where the address of the aggregate rather than its contents is passed into the procedure, but this allows the aggregate to be modified by the procedure (see later). In some implementations of ADA when aggregates are passed using 'in' mode they are passed by reference-constant, in which the reference is treated locally as constant within the procedure and so its elements cannot be updated, only referred to and copied. For all three of call by value, call by constant-value and call by reference-constant the value of the actual parameter is unaffected by the processes carried out within the procedure body. In QL SuperBasic the simple rule is that any expression [literal numeral, or variable *+-/ literal numeral or variable or similar combination] is passed to the procedure or function by value. The interpreter really can't do anything else. It has to evaluate the expression and then assign its result to a temporary storage location which is the formal parameter in the proc/func's declaration, i.e., x in Tim's example procedure inc(x) in QHJ #24. This x may be modified within the procedure (call by value). Mechanisms for Passing Data out of Procedures. ---------------------------------------------- Most languages do not have a specific mechanism for passing information out of procedure (unless the parameter is both in and out), but ADA supports call by result which is implemented as an 'out' mode parameter. Such 'out' mode formal parameters act as if they are uninitialized local variables on entry to the procedure and the last value that they receive during the procedure is returned in the actual parameter. So in ADA you can get this sort of thing: procedure read_neg_num( negative : out integer) is number : integer; begin get(number); while number > 0 loop put_line("Number not negative, try again"); get(number); end loop; negative := number; end read_neg_num; In Algol-W call by result is also used, but by a slightly different mechanism when compared with ADA. If the formal parameter is a structure, then the same local copy problems occur as with input parameters; so ADA allows aggregates to be passed 'out' by reference. Pascal and Modula, etc., don't support the call by result mechanism at all and use call by reference to return values to calling procedures. Mechanisms for Updating Data Passed to Procedures. -------------------------------------------------- The first mechanism that achieves updating is call by value-result which combines call by value and call by result. In this the formal parameter acts as a local variable, which is initialized to the value of the actual parameter. During the procedure's execution changes to the formal parameter only affect the local copy, but the actual paramater is updates to the latest value of the local copy on exit from the procedure. ADA achieves call by value-result for its 'in out' mode parameters, but uses call by reference (qv) for aggregates. The second mechanism is call by reference (and is by far the most common) and Pascal, Modula, C, FORTRAN, PL/I, SuperBasic, etc., are all capable of updating parameters using this approach. In some cases call by reference is the default (FORTRAN, PL/I) in others call by value (Pascal, Modula, ADA) and in others it depends on the parameter type - in C arrays are passed by reference as a default, everything else is by value. In SuperBasic when the actual parameter is the name of a variable [not contained in an expression], the interpreter has a choice - as Tim says in QHJ #24 - it can pass the address of the variable (its reference) or its value. All implementers of compilers and interpreters need to decide what to do in this situation as default. The default for SB is by reference, but it is simple to pass by value when required - you simply create an expression using parentheses around the variable name and the result is passed by value. There is another type of updating call mechanism "call by name" that was used in Algol 60. In this the name of the actual parameter effectively textually replaces the occurrence of the formal parameter in the procedure body, with the address of the actual parameter being recalculated each time it is needed. This is much more expensive to implement and in execution than call by reference, so it has not been used in more modern languages. Other Issues. ------------- Some languages (C++, ADA and others) support overloading of procedure and function names wherein different instances of the same procedure name are distinguished by their parameter types. For example in ADA, procedure swap(a,b : in out real) is begin ... end swap; procedure swap(a,b : in out integer) is begin ... end swap; The compiler sorts out which procedures is being called by inspecting the types of the parameters and then uses the approriate version. Normally actual parameters and formal paramaters are associated by the order in which they occur in the procedure call and procedure declaration, i.e., 1st formal paramater = 1st actual parameter, and so on. In PL/I and ADA it is possibly to make associations explicitly by name (Named Association), for example in ADA, procedure inc(val : in out integer; by : in integer) is begin val := val + by; end inc; and the call could be written in any of the following ways, inc( number, 2 ); inc( number, by => 2 ); inc( val => number, by => 2 ); or even, inc( by => 2, val => number ); - all with exactly the same effect! Some languages support the idea of default values (C++, ADA) for formal parameters so that if no parameter is supplied explicitly the default value is used. For example in ADA if 'by' is defaulted to 1, then, procedure inc(val : in out integer; by : in integer := 1 ) is begin val := val + by; end inc; inc(number); is identical in effect to inc(number,1); You can also pass procedure or function names as parameters in some languages - food for another article sometime... One thing that I try to do when writing procedures that I believe is a good idea is to only allow the code in the procedure to modify or use either local variables or parameters. In other words I make every effort NOT to use, and especially NOT to modify, global variables or non-local variables that are effectively global to the procedure under the scope rules. This makes it much easier to identify from the procedure code what is its precise effect. When I am constrained to modify globals or other non-local variables in scope then I tend to use comments to this effect in the code, e.g., procedure SomethingOrOther( var x : intger; var y : real ); (* GLOBALS: var a : SomeRecordType; {this is read-write accessible} b : SomeOtherRecordType; {this is read} *) (* NON-LOCALS: var q : boolean; p : integer; *) label ...; const ...; type ...; var ...; procedure ...; function ...; ... begin {SomethingOrOther} ... end {SomethingOrOther}; This makes it quite clear to someone reading the code that the identified variables are potentially modified by the procedure. It suits me - others will, no doubt, hate it, but I defend it on maintenance grounds. I would still prefer to pass the items as parameters even if the parameter list gets very long as a result. Some Thoughts on Functions. --------------------------- Functions and procedures only really differ in that the former return values directly to the calling procedure. In FORTRAN, ALGOL 60 and Pascal this is achieved by assigning a value to the function name within the function body. In later languages such as C and Modula a 'return' statement is used to pass a value to the caller. [NB In ADA, C and FORTRAN the return statement can also be used without an argument to exit a procedure] Functions can usually take both value and reference parameters too; so there is the possibility of functions returning lots of values!! This has to be a really bad idea, but the languages let you do it!!! In fact, ADA prevents 'out' and 'in out' mode for function parameters, but still allows the programmer to 'mess about' with globals and variables in scope - gruesome! . I believe that EUCLID and quite a few functional languages do prevent such social solecisms, but I only wish C, C++ (yes!), Pascal and others did too. We'd all be much better programmers (or atleast we'd produce better source code) if language designers reduced the possibility of side-effects that can be created (all too often unwittingly!) SOME THOUGHTS ON PROGRAMMING STYLE By Peter Tillier In QHJ #24 Tim talks about a colleague's style of writing Perl and contrasts it with his own. I have spent several years as a programming and system development lecturer within my company's internal training department and nothing seems to cause more grief/criticism/etc., etc., as differences of programming style. I tend to use procedure calls in preference to the use of deep nesting of 'if..then..else..endif' structures as does Tim's colleague. I do this for a number of reasons and even if the procedure may only be called once in the entire program (incidentally this approach is taken by Kernighan and Ritchie in 'The C Programming Language' and by Kernighan and Plauger in 'Software Tools in Pascal'). My reasons are these: it is sometimes inconvenient to read deeply nested 'if..else..endif' or 'while..endwhile' constructions; this approach works very well with the program design method that I prefer to use (Jackson Structured Programming, aka. JSP); if suitable procedure names are chosen the clarity of the code is often improved; the style is closer to the object-oriented programming approach that I would prefer to use; the arguments about inefficiency ("It's wasteful to set up a stack frame and call code that could have been inline.") take little account of the maintenance benefits that can accrue from well-designed and named procedures. I find something like this much easier to follow (and debug!), procedure DoLotsOfThingsTo(var A : AType); var i : integer; procedure DoOneThingTo(var A : AType ); begin A.A := ...; A.B := ...; end {DoOneThingTo}; begin for i := 1 to SizeOfAType do DoOneThingTo(A); ... end {DoLotsOfThingsTo}; (the above also shows one reason that I like Pascal - the ability to nest procedure declarations - I miss it a lot when I use C, C++ or things like Visual Basic). Incidentally, Question: can you nest procedure definitions in SuperBasic? Answer: Yes you can: 1000 define procedure testa 1010 : 1020 define procedure testb 1030 print "In testb" 1040 end define testb 1050 : 1060 print "In testa (1)" 1070 testb 1080 print "In testa (2)" 1090 : 1100 end define testa works perfectly, printing out, In testa (1) In testb In testa (2) as expected. As I said in my article on parameters and parameter passing mechanisms I think that most languages would be better if they were designed so that procedures and functions (in the SB or Pascal sense) could only access local variables or parameters - even for read access only. SOFTWARE REUSE For years I've reading articles on Software Reuse and how it can increase the productivity of programming shops. Since I program alone, as most QLers programmers do, I have never given it much thought for my programming. For some reason, a recent article on software reuse sparked a new thought about software reuse and the QL. Before I go into my sparked thought, I want repeat here one sideline from the article. The Eight Commandments of Reuse: 1. Golden rule of reuse: encourage individuals and teams to behave in ways that support reuse. 2. Keep an inventory of reusable artifacts. 3. Provide a catalong with descriptors and search support. 4. Desigate a reuse administrator/facilitator who keeps the catalog and helps users. 5. Develop a methodology outlining how and when to reuse software components. 6. Have a measurement program to track reuse and adherence to the methodology. 7. Design standards that specify how artifacts are contructed. 8. Adhere to a quality-assurance program to guarantee the integrity of artifacts. Now that you have read the above, set it aside for the moment (for you Assembly programmers, PUSH it. You will need to POP it later). I think one of the most difficult areas of writing programs for the QL is dealing with the Pointer Environment. You either buy a PE toolkit (such as EasyPointer) and a SuperBasic Compiler (QLiberator) for a fair amount of dollars, or, you can program in C with C68 and the Pointer libraries. Being cheap, I would opt out for C68, but I am very weak with full C (OK, I write a few hacks in C, but I am no where near calling myself a C programmer). Using C68 and the Pointer Environment is not trivial. It's not something for the fledgling C programmer. For those that do program in C and the Pointer Environment, each programmer is writing a lot of the same display routines to get output to the screen. For some this is not easy and takes up some significant time and effort. OK, now POP what you had PUSHed earlier; software reuse. What if a number of C68 programmers were to get together (just like they do in the development of C68) and started collecting a library of C68 PE routines that could be used by other programmers? Kind of sounds like software reuse. If QLers where to use the 8 Commandments of Reuse, we would only need to use commandments 2, 3, 4, 5, and 7. We would not need to track who uses reuse or who does not. If someone was to volunteer to be the administrator (they would need to be a C68 programmer), other C68 programmers could send in thier functions and procedures to be added to a library. This could be documented and then distributed back out to C68 programmers. Submiting functions and procedures may require some code changes on the part of the submitting programmers. The functions and procedures would have to be written in a more portable "black box". No use of global variables. I don't know if the time and effort put into this would save any programmer time in the long run. The time saver for the programmer would be the time saving in having to re-write code that has been written before. Would this time savings be enough to warrant the cost of organizing the library? Again, I don't know. I just thought it might be worth considering. Any takers? DBAS For most database programming, the QL programmer has been pretty much stuck with Archive. Archive is a fine language and is fairly similar to dBase III in programming feel. It has many advantages: editing of records built in, easy screen creation, a well structured language. But it also has a few weak points: limited functions, little control over end user accessing code, relatively slow. If you are looking for a database development system that allows you to create stand-alone code, full access to features of QDOS, relatively fast, and free, then DBAS is something that you should look into. DBAS, also called Database Handler, is a library of database handling routines for SuperBasic, C68, or Machine Code. DBAS is not a database language system like Archive, so it is not a true replacement for Archive. The core part of DBAS resides in DATA_BIN. It is loaded by LRESPR. DATA_BIN contains the main routines for database handling, but only for Machine Code programs. If you use SuperBasic, DBAS_BIN contains the SuperBasic interface to DATA_BIN, and it too is LRESPRed. For C68 programmers, there is a library of database routines that access DATA_BIN. Programming with DBAS is not as easy as programming with Archive. You are using DBAS for database function calls, but you still have your programing control constructs (looping, branching, etc) in SuperBasic or C68. What you loose in ease of programming from Archive, you gain in power of programming. Since you are using SuperBasic or C68 to program in, you still have the full power of either language and all that they can do. DBAS has both procedures and fuctions. A sampling of procedures is: ADD_FIELD Add a field APPEND Add a new record CREATE Create a database EXCLUDE deselect records FIND Find by INSTR INCLUDE Select records LOCATE Find by ORDER paramaters OPEN_DATA Open a database ORDER Order a database REMOVE Delete a record SEARCH Find by INCLUDE paramaters UPDATE Update a record A sample list of fuctions is: COUNT Get record count FETCH Get record contents FLLEN Get field length FLNAME Get field name Databases are treaded like files and are opened with the OPEN_DATA procedure. After that they are refered to by their channel number. Fields do not specifially have names - they are referenced by field number - but field names can be implemented with some work arounds. DBAS does not seem to prohibit opening more than one database at one time, but I do not see in any of the commands where you can specify a JOIN ( selecting records from two databases/tables with a common equality). By doing a couple of searches on each database, you should be able to rig up the equivalency of a JOIN. Since DBAS does not have a front-end for doing database creating, editing databases, etc, two utilities come with DBAS to make maintaining individual records easier. DBPTR_BIN is a Pointer Environement program for editing, adding, and deleting records. For non-PE users, there is ALTER_BIN. Both of these programs are executables. DBAS has a lot of potential. Since it is LRESPRed, it is compatible with SuperBasic compilers, like QLiberator. You can compile your code and create a stand alone application. The _BIN files are freeware and can be distributed with your program. DBAS comes with full documentation for all of its features, including the SuperBasic, C68, and Machine Code interfaces. It comes with example programs that help in learning how to use DBAS. If you are new to databases and you want to learn how to program them, stay with Archive. If already know how databases work and want to develop your own stand-alone applications, then DBAS is worth the look. DBAS should be available on most QL BBS's worldwide. For North American QL users, you can get it from QHJ Freeware (me) (just send a disk with return postage).