Subject: Writing Reusable 4GL From: kerry@kcbbs.gen.nz (Kerry Sainsbury) Date: Tue, 27 Jun 1995 20:15:59 +1200 I've been wanting to write this document for some time. I hope it will inspire some 4GL programmers to look at modular variables, and see how fantastic they can be for code reuse. A reasonable introduction to classes too, for those on an OOPL path... (although I think I may have got some of the OO jargon wrong) BTW: I'm keen to get a debate going if there are people out there who disagree with my enthusiastic rantings. Regards, Kerry S ------------------------------------,------------------------------------------ Kerry Sainsbury, kerry@kcbbs.gen.nz | THE INFORMIX FAQ v2.2 Mar 95 Quanta Systems, Auckland | kcbbs.gen.nz:/informix/informix.[faq|apx] New Zealand. Work: +64 9 377-4473 | mathcs.emory.edu:/pub/informix/faq/ " " Home: +64 9 279-3571 | http://www.garpac.com/informix.html ------------------------------------------------------------------------------- WRITING REUSEABLE INFORMIX-4GL CODE (or: Why I love Modular Variables) Kerry Sainsbury, July 1995 I want to explain how to create easily reuseable and extendable software components. Object Oriented Programmers use some of these buzzwords, we can too if you like: Encapsulation: All code related to a task lives in a single place, with the actual implementation hidden from the user code. Inheritance: The ability to take existing functionality and extend it without breaking existing user code. ENCAPSULATION Imagine we need a program to control a simple vending machine :-) Each machine needs to be able to: - accept money - accept product selection - deliver product - give change - record the sale We will need to write a different program for each product manufacturer to cope with their own special requirements, but these are the essential functions. Here are some solutions we could adopt: A. The MAIN way. We could code the entire routine in one MAIN section, without any functions, and use local variables to keep track of data. main_eat.4gl: MAIN DEFINE l_money, l_product, l_cost, l_change... DISPLAY "EAT FOOD" -- Special code for this vendor INPUT l_money... INPUT l_product ... DISPLAY "Here is your ", l_product SELECT l_cost FROM price WHERE product = l_product LET l_change = l_money - l_cost DISPLAY "Here is your change: ", l_change INSERT INTO sales(product, day) VALUES (l_product, TODAY) DISPLAY "THANKS FOR EATING FOOD" -- More vendor-specific code END MAIN This is a poor solution because the only way we can reuse this code (for example in a later model machine with the ability to disallow selection of out-of-stock product, or the ability to vend coffee) is to cut and paste the code into a new program. Similarly if we later wish to change the recording of sales to include TIME of purchase we will need to remember to alter both the original vending program, that of the later model, and that of the coffee machine. B. The GLOBAL way. We could code each routine individually and communicate between the routines via global variables. main_drink.4gl: GLOBALS g_money MAIN DISPLAY "DRINK SUGAR" -- Vendor specific CALL enter_money() DISPLAY "Thanks for the $", g_money -- Vendor specific CALL enter_product() CALL deliver_product() CALL give_change() CALL record_sale() END MAIN vend.4gl: GLOBALS g_money, g_product, g_cost, g_change... FUNCTION enter_money() INPUT g_money... END FUNCTION FUNCTION enter_product() INPUT g_product ... END FUNCTION FUNCTION deliver_product() DISPLAY "Here is your ", g_product END FUNCTION FUNCTION give_change() SELECT g_cost FROM price WHERE product = g_product LET g_change = g_money - g_cost DISPLAY "Here is your change: ", g_change END FUNCTION FUNCTION record_sale() INSERT INTO sales(product, day) VALUES (g_product, TODAY) END FUNCTION This looks like a reasonable solution except that a maintenance programmer has no idea what function sets "g_money" on the vendor- specific line in MAIN, and sharing global variables between programs is a VERY messy business. The programmer also needs to somehow know that "g_money" is the global variable he needs to use, and hope that "g_money" doesn't already exist in his program when he finds he needs to add an interface to vend.4gl (remember in the real world it's unlikely to be something as clear-cut as a vending machine!) C. The LOCAL way. We could code each routine individually and communicate between the routines via local variables. main_chocolate.4gl: MAIN DEFINE l_money, l_product, l_money, l_cost, l_change DISPLAY "EAT CHOCOLATE" -- Vendor specific LET l_money = enter_money() DISPLAY "Thanks for the $", l_money -- Vendor specific LET l_product = enter_product() CALL deliver_product(l_product) CALL give_change(l_product, l_money) RETURNING l_cost, l_change CALL record_sale(l_product) END MAIN vend.4gl: FUNCTION enter_money() DEFINE l_money INPUT l_money... RETURN l_money END FUNCTION FUNCTION enter_product() DEFINE l_product INPUT l_product ... RETURN l_product END FUNCTION FUNCTION deliver_product(l_product) DEFINE l_product DISPLAY "Here is your ", l_product END FUNCTION FUNCTION give_change(l_product, l_money) DEFINE l_product, l_money, l_cost, l_change SELECT l_cost FROM price WHERE product = l_product LET l_change = l_money - l_cost DISPLAY "Here is your change: ", l_change RETURN l_change END FUNCTION FUNCTION record_sale(l_product) DEFINE l_product INSERT INTO sales(product, day) VALUES (l_product, TODAY) END FUNCTION This version greatly increases programmer understanding of the data flow between the routines, but introduces many variables to the calling program which aren't actually required by it. The only variable required by main_chocolate.4gl is "l_money", with the other four variables being passed back and forth only for vend.4gl's benefit. Hardly encapsualisation! MAIN_CHOCOLATE SHOULD NOT NEED TO KNOW HOW VEND.4GL WORKS. If a decision is later made to record the amount of change in the "sales" table then each main_.4gl would need to be altered to pass l_change into record_sale, so that it could be INSERTed into "sales". D. The MODULAR way. We code the routines individually and communicate via modular variables. Yes - this is the sexy solution: main_condom.4gl: MAIN DEFINE l_money DISPLAY "HAVE SAFE SEX" -- Vendor specific CALL enter_money() LET l_money = get_vend_money() DISPLAY "Thanks for the $", l_money -- Vendor specific CALL enter_product() CALL deliver_product() CALL give_change() CALL record_sale() END MAIN vend.4gl: DEFINE m_money, m_product, m_cost, m_change... FUNCTION enter_money() INPUT m_money... END FUNCTION FUNCTION enter_product() INPUT m_product ... END FUNCTION FUNCTION deliver_product() DISPLAY "Here is your ", m_product END FUNCTION FUNCTION give_change() SELECT m_cost FROM price WHERE product = m_product LET m_change = m_money - m_cost DISPLAY "Here is your change: ", m_change END FUNCTION FUNCTION record_sale() INSERT INTO sales(product, day) VALUES (m_product, TODAY) END FUNCTION -- Here are the new routines... FUNCTION get_vend_money() -- NEW!!! RETURN m_money -- NEW!!! END FUNCTION -- NEW!!! FUNCTION get_vend_product() -- NEW!!! RETURN m_product -- NEW!!! END FUNCTION -- NEW!!! This, in my humble opinion, is just glorious. The main_condom.4gl is clear and uncluttered, and has entirely no idea about how vend.4gl goes about its business. Interfaces to vend.4gl variables are via the get_ functions. main_ routines need only call the get_ functions for the variables they are interested in. As new functionality is required it is only vend.4gl which requires modification - all main_.4gls just need recompiling. For example: Recording change in the "sales" table is just a matter of adding m_change to the list of inserted columns. INHERITANCE I've shown how we can encapsulate functionality via modular variables. Now I want to show how we can use modular variables to expand on the basic functionality of our vending machine, to include features specific to new vending machines without breaking our old machines. - New vending machines are able to identify when they are out of stock of a product. New vending machines would have a main section looking like this: main_new.4gl: MAIN DEFINE l_money DISPLAY "BUY *NEW* THINGS" -- Vendor specific CALL define_new_machine() -- NEW!!!! CALL enter_money() LET l_money = get_vend_money() DISPLAY "Thanks for the $", l_money -- Vendor specific CALL enter_product() CALL deliver_product() CALL give_change() CALL record_sale() END MAIN vend.4gl: DEFINE m_money, m_product, m_cost, m_change, m_new_machine -- NEW!!! [ unchanged routines not reproduced here ] FUNCTION define_new_machine() LET m_new_machine = TRUE END FUNCTION FUNCTION enter_product() DEFINE l_got_product LET l_got_product = FALSE WHILE NOT l_got_product INPUT m_product ... IF m_new_machine THEN SELECT onhand FROM stock WHERE product = m_product IF onhand = 0 THEN DISPLAY "Sorry - out of stock of ", m_product ELSE LET l_got_product = TRUE LET m_new_machine = FALSE END IF ELSE LET l_got_product = TRUE END IF END WHILE END FUNCTION The new "define_new_machine" function simply sets a flag for later use inside "enter_product". The beauty of this is that when we recompile vend.4gl against our *old* programs the code will not break - old programs don't set m_new_machine to TRUE, so none of the code dependant on that variable will be executed. Note that it's important to reset such flags to FALSE as soon as you've finished with them so that subsequent calls to the routine don't pick up your settings. For example: Imagine a routine which usually displays a message in RED, BLINKing text on line 3 of your screen, but on some occasions needs to display on other lines. ugly_blink.4gl DEFINE m_row SMALLINT FUNCTION define_blink_line(l_row) DEFINE l_row SMALLINT LET m_row = l_row END FUNCTION FUNCTION display_red_blink() IF m_row = 0 THEN LET m_row = 3 END IF DISPLAY "UGLY MESSAGE" AT m_row, 1 ATTRIBUTE(RED, BLINK) LET m_row = 0 -- Reset row indicator END FUNCTION It's important that display_red_blink() resets m_row otherwise the following code would display on line 5 twice, rather than line 5 and line 3: CALL define_blink_line(5) -- Set to line 5 CALL display_red_blink() -- Display on line 5 and reset to normal CALL display_red_blink() -- Now display on normal line 3 Let me know what you think of this technique. Thanks, Kerry S ------------------------------------,------------------------------------------ Kerry Sainsbury, kerry@kcbbs.gen.nz | THE INFORMIX FAQ v2.2 Mar 95 Quanta Systems, Auckland | kcbbs.gen.nz:/informix/informix.[faq|apx] New Zealand. Work: +64 9 377-4473 | mathcs.emory.edu:/pub/informix/faq/ " " Home: +64 9 279-3571 | http://www.garpac.com/informix.html ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From: mar.greco@agora.stm.it Date: Wed, 28 Jun 95 07:16:45 PDT I think we should have more of this stuff here. I'm so interested in reusable code that I wrote my own message orientented application framework, much in the style of OWL (...only smaller). Currently this afw is used in the development of a 62 table radiotherapy DB. If anybody is interested, maybe I could write something on it. Marco. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From: "John H. Frantz" Date: 28 Jun 1995 11:04:08 GMT In article , kerry@kcbbs.gen.nz (Kerry Sainsbury) wrote: > I've been wanting to write this document for some time. I hope it will > inspire some 4GL programmers to look at modular variables, and see how > fantastic they can be for code reuse. > > A reasonable introduction to classes too, for those on an OOPL path... > > BTW: I'm keen to get a debate going if there are people out there who > disagree with my enthusiastic rantings. >..... > Does everybody use these techniques already? (I know our shop doesn't) > Is there nobody else who cares about this sort of stuff? >..... Yes I use these techniques extensively (and like with yourself it came to me like an awakening). Some object oriented principles are in fact possible with 4gl although somewhat clumsily. My Power-4gl user interface package (see http://www.strengur.is/~frantz/pow4gl.html) makes use of the ideas you mentioned. In fact, I never use global variables anymore. The only part of 4gl that doesn't lend itself well to modularization are the screen interaction statements (i.e. input). Probably not a coincidence that New Era contains 4gl except for the screen interaction statements. John H. Frantz (frantz@centrum.is) Alive in Iceland! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From: marco greco Date: 4 Jul 1995 06:04:31 GMT Well, it looks like somebody has already done some major work on reusable 4gl code (I've given a quick look at POWER-4gl and I must say it looks good!), so here are my two cents on the subject. I started thinking about an application framework back in 1992, when the users of my radiotherapy info system begun complaining that it was too mastodontic (you had to go six or seven levels down a menu before getting to do somenthing, and had to go all the way up plus some more levels down to do something else); in the meantime some guy at the local university radiology department asked me to write a system which could generate a set of multiple choice questions he could use to prepare exam papers and his students to test their knowledge. I wanted to reuse quite a bit of the code I already had written, plus I needed a simpler interface, something that, for example, allowed the user to input on the fly a new record of some kind and after that get back to whatever the user was watching (which would be totally unrelated), and this without having to put in each "viewing" module the knowledge of what all the "input" modules were about! After some thought (and a quick glance at the turbo vision library manual!) I came out with the message oriented application framework I'll try to explain in the following few ;-) lines. Of course 4gl lacks fancy things like user definable constants or types, not to mention pointers, procedural parameters or *objects* (I'm not an OO fan, in the sense that I haven't had a chance to use OOP, but I like the idea), so 4glWorks (that's its name - not much fantasy, I know, but I spent more time working on it than thinking of a name) works in the exact opposite way than TV: modules waiting for a message actively do so on the stack, rather than passively sit on a heap I don't have,waiting to be called upon message arrival. The resulting operation, however, is similar to that of TV, with a few limitations (SDI vs MDI, windows are not movable or resizable). Before trying to explain 4glWorks, have a look at the typical 4glWorks application in this block diagram: Application | 4glWorks ~~~~~~~~~~~ | ~~~~~~~~ | | ->Services<- | | ^ | | | | v | | | Vertical Menus<--|----->Vertical Menu Library | | ^ | | | | | | v v | | Horizontal menu<------|----->Horizontal menu Library | ^ | | | | | ----------------|----->Scrollers | (|) | ^ |------- ----------------|----------| | | | v v | Viewers | | | v | Main Program | The idea is quite simple: the application is made up of a number of modules that call each other and exchange messages in order to comunicate what each wants the other to do. Messages are a couple of smallints that travel upwards (as parameters passed to the next module) and downwards, as values returned. Let's have a closer look at each type of module: "Viewers" stay at the bottom of the stack: they serve to handle completely a particular type of data (which may be complex, i.e. multi table, etc). A viewer is responsible of the retrieval of the data and its display on the screen. As such it must know how to: - display the data on demand (e.g. display a form, fill it with data, scroll a screen array up or down, etc) - perform db related functions on demand (open, close & free cursors, fetch rows) Depending on what the viewer does, it may be capable of performing, at the user request, actions on the data it handles either directly or with the aid of "Services". Such actions include: - the selection of data to be retrieved - data manipulation (insert/delete/modify records) - data printout "Services" are the modules used to carry out actual data manipulation. Horizontal & vertical"Menus" interface directly with the user, allowing him to choose the operations he wishes to perform. Menus wait for the user to select an action and then either call a service to perform whatever the user wants to be done, or pass the request back to the current viewer. Any given message can be associated with any of the following: - a horizontal menu entry - a vertical menu entry - a cursor or control key also, menus can be freely cascated, and these to facts allow the programmer to (re)design the user interface without modifications to viewers or service modules. Services can be called either by viewers or by menus, depending on the programmer wish, to allow to perform common, data type related tasks (insert, delete, modify records) without having to inform the menu on what to do, & to perform viewers unrelated operations without replicating such knowledge on the viewers themselves. The beauty of the system is in the fact that each service, however invoked, is free to screw up the screen (or the DB) in whatever way it needs to: on return a message will inform the viewer of the actions it needs to take to return to a safe state. The service may for instance open & display a new form, perform data input and insert a new row on a table; the return message will inform the viewer to redisplay its form, redisplay the data and, if the row was inserted in a table being viewed, to reopen the cursor and fetch all the needed rows to reflect the table changes. Viewers will not need to know what has been done, or who has doen it: all they need to restore their previous state is to know what to do next! Messages to the current viewer include also request to carry out operations like scroll up, scroll down the video, etc and most important, request to close down and return to the main program to either stop the application or switch to another viewer. The main program is only a viewer spawner: just a while statement with a big case inside. Having more than one viewer makes the application *modal*: the mode of operation depends on the capabilities of each viewer; apart from that services would make the application modal anyway: on a given moment the application could be in viewing mode, select mode or input mode. Now, I can hear you saying "Ok, this looks great (maybe!), but where's the code reusability?!?, I'll have to write all the data display functions from scratch!" Don't panic, we have "scrollers"! Scrollers take all the hassle out of viewer development by placing all the common data retrieval & display function in one convenient module. A viewer written to use a scroller sends & receives messages from/to this rather than to the menu system. The scroller filters messages from the viewer and the menu, performing all the data display requests and leaving untouched all others, so that the viewer needs only to service data manipulation requests. And, if a scroller doesn't exactly do what you want (say you want a nice caption under the data), it is always possible to put a "filter" that does what you want between the viewer and the scroller! If I am not mistaken, we are talking about encapsulation and inheritance... Though not mentioned on the previous chart, "Filters" are used throughout 4glWorks application to perform common actions on messages (e.g. services use a common filter - end_modal - to filter all the status display messages, and leave the scroller or the viewer only with a data display message) One last point to note is that all viewers may (and indeed will) share a common menu system, but if needed, each viewer could have its own. 4glWorks is a working application that serves no useful purpose other than display itself and let the user exit, but knows how to do many common tasks. It is a great starting point to develop new applications, since the programmer only has to bother to develope new viewers to be put in the main program and new services to be linked with menu items and/or viewers, this without changing a single line of the existing code. Currently 4glWorks boosts: - 4 different scrollers: a single table browser, with the ability to do full record or brief (e.g. code & description) display a similar dual table scroller a text scroller capable of displaying records from different tables * in various orders * with links to each table and record, to allow for data modification/deletetion * with printing capability a simple text browser with printing capability - 3 viewers: a null (empty) viewer, usually the entry viewer a simple sql interpreter an application error log viewer - horizontal & vertical menus support, with key trapping and viewer dependent help messages: the programmer only needs to set up the menu structure (entrys & associated messages), which can be dinamically changed - services that lock/unlock tables or the current database - single & multiple selection field help display routines: with the ability to search tables, get data from pipes or multiple files, the ability to find the next/previous entry whose code/description begins with a given character and the ability to find, when invoked, the entry that is closest to the field contents. - printing support in the form of a printer table, and print library routines with the ability to produce variable length reports (not a novelty!) and to include byte data (i.e. images, logos, you name it...) in headers or footers - language support that allows to run the same executable in different languages without modifications or recompilation - various text / display library functions, common dialogs and filters to perform all common display tasks - a complete error loging facility that includes tracking of unexpected messages and unfound message files - file & os oriented routines - needles to say, function naming is consistent througout 4glWorks, so as to fake method inheritance, plus most of the variables are modular, though some global data is used (e.g. "viewing" informs the modules of the current viewer) All this in 6500 lines of 4gl, 500 of c and a few forms. Future plans: A major revision of printer definition data, which, at present must be replicated on each DB. I am planning to put them on a common DB, or give the programmer/user the choice, or to move the data to the file system. Anyone wishing to know more about 4gl works please write me, as I don't have at present a place to put a more detailed description (my employer is not on the net, and Agora` doesn't offer this service). Suggestions on such a place much appreciated. Thanks for the time spent reading this article. Marco ........................................................................ tear along dotted line! marco greco mar.greco@agora.stm.it Work Achea Srl tel 39 95 503117 / 447828 Pbx Catania, Italy fax 446558 Home tel 39 95 277643 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++