Making the procedural/relational paradigms more
In debates with OO fans, one often finds that criticisms directed toward procedural/relational (p/r) paradigm(s) involve flaws or limitations with specific languages, most notably the often awkward C (apologies to C fans). These are usually language-specific criticisms and not inherent flaws in p/r itself. Thus, they attempt to throw out the baby with the bath water.
Another complaint is that p/r results in the usage of excessive global variables. An actual look at cases where global variables are used often reveals situations in which additional p/r language constructs could have been used instead. (In some cases it is nothing more than bad design on the programmer's part.)
This document describes some of the constructs which make p/r more competitive with object oriented languages and other paradigms.
Run-time evaluationRun-time evaluation allows expressions and program code to be stored and created in tables and string variables. They essentially allow "programs to write programs". They can be found in Control Tables. They are also useful for procedural/relational versions of the Strategy and Observer pattern and other areas where C-like address pointers would otherwise be used. They have the following advantages over address pointers:
- They produce interpreter/compiler-trappable errors instead of potentially rewriting who knows what in memory. (A pointer error in one of my C programs once rewrote the base I/O routines without warning and did very odd things not related to the position of the original error.)
- Easier to pass parameters.
- Can be stored in external files and tables
x = "print('Hello World')" eval(x) // result: "Hello World" // Observer pattern usage observeUs = "foo; bar; yaz; taz" // routine names for each i in split(observeUs, ";") eval(i & params) // append std. param list endForThere are also more advanced forms of run-time evaluation, but an Eval-like operation is usually sufficient. I have even kicked around the idea of having entire routines stored in tables. This could make finding and cross-referencing routines much easier. Why build all this into an IDE when RDBMS have most of it built-in already?
Note that it is probably best to use run-time expressions only in cases where programmers or technical operators maintain the table, and not in cases where application users maintain tables (which would usually be via a UI). For example, a programmer may manage a data dictionary, but not a corporate department list.
Nested RoutinesNested routines allow variable scope to be "inherited" by the called routine. This can simplify parameter passing, especially when a subroutine grows too large or complex and has to be split, but you don't want to pass a jillion parameters that used to be local working variables.
Another situation is implementing generic string and variable melding, such as implementing a Perl-like dollar sign variable substitution in strings. See the dollar sign implementation example
Inheriting scope can also reduce the need for global variables for variables that are shared by a parent routine and children.
There are at least 3 approaches to subroutine nesting. The first is physical nesting, as found in Pascal. The second is linking by naming. A subroutine can declare itself is being the child of another routine. The 3rd approach is for a subroutine to declare itself as inheriting any scope from it's caller (except for variables explicitly declared as being local in the child). The 3rd approach allow more genericy than the other two approaches in my opinion because it can be called from any routine. (However, it reduces ability to do compile-time checking and optimization.)
In some languages, such as Pascal, the name of the nested routine is only recognized within the parent routine. This can reduce routine naming conflicts, but requires more complex syntax for the parent to distinguish between same-named children routines and outside routines. (Some sort of packaging scheme perhaps would be preferable to solve this issue.)
Named parametersNamed parameters are very useful in situations where there are many potential options available, but only a small subset tend to be used at any given time.
They are also useful for adding parameters without changing every existing subroutine call. (Some languages are picky about having every call have the same number of positional parameters.)
invline("Subtotal", subtotal, #color red) invline("Tax", tax) invline("Shipping", shipAmt, #color green #italics) invline("Total", tot, #italics #bold #color yellow #font_color black)In this consumer invoice line example (roughly based on L syntax) the # symbol marks a named parameter. Note how some parameters do not need sub-parameters. For example, "#bold" stands by itself. Some forms of named parameters would instead require something like "bold=true".
Implementing the above routine with positional parameters would require at least 6 positions. It would be hard to remember which attribute was associated with which position. As a general rule of thumb, named parameters are a likely candidate if a routine needs more than 4 or 5 parameters, especially if only some of them are required or infrequently used.
An alternative is using a dictionary array (also known as an associative array).
var x x.title = "Shipping" // same as x['title']="Shipping" x.value = shipAmt x.italics = true x.color = "green" writeIt(x) // or x.writeIt()This approach resembles OOP syntax. Note that "x.writeIt()" is equivalent to "eval(x.writeIt)" in L syntax. The syntax of associative arrays differs per language.
Field SyntaxIf one works with a lot of tables and/or record or result sets (from RDBMS's), then some sort of syntactical mechanism should be set up to to allow fields to be easily accessed and referenced.
rs = OpenRecordSet(.....) rs.setField("zipcode", "12345") // lousy setField(rs, "zipcode","12345) // still lousy rs.zipcode = "12345" // better x = rs.getField("zipcode") // lousy x = getField(rs,"zipcode") // still lousy x = rs("zipcode") // slightly lousy x = rs.zipcode // betterNote that the "dot syntax" is not isolated to OOP languages. In some languages it represents built-in table syntax, in others it is an associative array (also known as a "dictionary").
Dictionaries (Associative Arrays)Dictionaries are a type of array that allows one to associate name and value pairs. They can be found in Perl, Python, later versions of Visual Basic, and many others. In addition to "associative arrays", they are sometimes also called "hashes" or "hash arrays".
Although I disagree with heavy usage of dictionaries for data handling, they make wonderful interface packaging mechanisms. For example, I had an application that has a bunch of message handling global variables that dealt with the timing, polling, and handling of message files. If the language had dictionary arrays, I could put all these variables into a dictionary to give them a kind of grouping, but without the bloat and syntax overhead of a dedicated class.
The syntax for dictionaries varies, but one approach is to allow the "dot" syntax if there are no embedded spaces or odd characters in the index. (In such cases, use something like brackets instead). In some ways a dictionary acts a lot like an instance variable syntactically.
var bob // declare dictionary bob["shoe_size"] = 9 // traditional syntax bob["age"] = 40 bob.age = 40 // dot syntax bob.shoe_size = 9 // dot syntax print bob.shoe_size // result: 9Sometimes one can even put program code into dictionaries to get a class-like feel (at least in an OO scripting sense, for fans of strong-typing would not like it). This triggers interesting chicken-egg debates about whether OO classes are really dictionaries or dictionaries are really classes. (Your answer probably depends upon which you like the most.) A reserved dictionary entry, something like "~sys_parent", can even be used to create dictionary attribute inheritance in theory.
Case Statement ImprovementsCase statements are a favorite whipping boy of OO fans. Most of such criticisms do not stand up to close scrutiny and balanced thinking. (See Shapes example.) Not only are the structural arguments against them faulty, but many of the attacks are based on syntactical issues that are really language specific or a matter of terminology ( "blocks" versus "routines" versus "methods", etc. Sometimes it is useful to distinguish between "named blocks" and "unnamed" blocks, although the differences between such may actually be very small in some languages.)
The biggest syntactical complaint is the annoying "break" statement found in C and Java and their derivatives. Not only are break statements extra syntax, but if you forget to include one, you can have some hard-to-find bugs. This alone would probably be enough to drive me to OOP.
Fortunately, other languages are not so stupid. (Some say C did it this way so that multiple case blocks could optionally be executed, but IF statements are better for that sort of thing.)
In the Inheritance section of the Shape example you can see a more rational case statement example. (It barrows from Visual Basic. Note that this is not a general endorsement of VB. VB has it's own set of flaws.)
Column ViewsTable-level views are readily available for most relational database systems. However, I believe that a finer granularity is often needed: column-level views. For example, rather than keep appending together FirstName, MiddleInitial, and LastName; it would be nice to be able to define a column called "FullName" that produces the appended view for us. It would essentially serve as a read-only field.
We can do this with table-level views, but would have to rename any reference to our original table in any existing code to get it. We just want to add a (virtual) column, not a new table in into the table name-space.
Static VariablesThe Static clause allows variables to keep their value between subroutine invocations. (Static can be found in C. It is one of the very few nice things about C.) Without them, one may have to use global variables to achieve the same thing. (Although this is usually not a terrible thing, some software engineering philosophies dictate that globals are pure evil. In larger projects globals may be more of a problem than in small and medium projects.)
PackagesPackages allow for variable scope other than just "local" or "global". Packaging also helps resolve subroutine naming conflicts that may occur in larger software systems. See Packages and Naming Conflicts under the Language Options document for some variations of package syntax.
Table DocumentationIt is unfortunate that many relational/table database systems do not have a comment area for fields and tables. Along with field size, type, etc, a table schema should have spots to place longer descriptions and notes about fields and tables.
Although Data Dictionaries can do some of this, a better place is the base data scheme. (Data dictionaries may include calculated fields, virtual fields, and other information that may not be one-to-one with actual fields.)
This applies to tables as well as fields. For example, a long description of each table, and the primary key (if applicable) can be included.
Better Relational or Table Query LanguageMost people associate "relational" with SQL. However, SQL is simply a single manifestation of a relational language. Unfortunately there has not been much pressure for alternatives for some reason.
In my opinion SQL is far from ideal. For one, it relies too much on nested clauses when referenced (by-name) clauses would be preferable for non-trivial query statements. Reference-based would allow application query designers to design ad-hoc (temporary) view-like constructs, for example, to simplify or partition the query text into easier-to-relate-to sections or clauses. Large SQL statements can be like trying to figure out a paragraph-sized run-on sentence on a university reading exam. (Gee, I hope my web-pages don't have any of those.)
L Draft Language
Table Oriented Programming
Main | OOP Criticism
© Copyright 2000 by Findy Services and B. Jacobs