Tips and suggestions that improve the usability of procedural languages:
- NestedFunctions - A nested function automatically inherits a parent's scope. Very useful for reducing parameter complexity for functions that are closely tied. Pascal has these. An alternative for a dynamic language is to allow any routine to inherit scope of caller if a keyword/modifier is given.
- Associative tree - A dynamic structure that is a cross between a map and a tree and can be used as either. JavaScript sort of has these. For example:
var foo[];
foo.bar = 'blah';
foo.bar.snig = 7;
foo['bar']['snig'] = 7; // equiv to prior, allows punctuation in "keys"
print(foo.bar); // result: blah
print(foo.bar.snig); // result: 7 (some langs don't allow this)
.
- Multiple returns - Ability to return multiple values. Perhaps associative arrays can be used instead, but tend to be verbose. Python allows such.
(ret, errCode) = myFunct(blah);
- Named parameters or "flex" parameters, see UniversalStatement
- Multi-line text quoting - Useful for SQL and markup (XML, HTML) embedding.
- Inline Data Support - ability to easily inline arbitrary data constructs, ideally optimized. It's a real pain to define data (e.g. build 'fixed' tables or values) procedurally.
- ExplicitManagementOfImplicitContext - consistent way to manage execution context and environment without passing around massive ContextObjects or structures from procedure to procedure. Improves modularity and code reuse for UnitTesting (because you can dummy up the OS services, singletons, globals, and such via indirection through the context), and allows one to encapsulate security features far more readily.
- Control of non-linear Workflows (aka Concurrency) - ability to issue commands to occur in parallel as well as commands to occur sequentially. Ability to synchronize in accordance with common workflow languages. ProceduralLanguages aren't inherently event-driven, and the underlying implementation of these doesn't need to be multi-threaded (indeed, it could be sequential except to the point that a wait is incurred). It would be much better to have support in the language for these things than some sort of hacked-in unportable OS library.
- SoftwareTransactionalMemory - support for atomic state updates. Useful even on a single-processor machine when it comes time to handle errors detected halfway through an operation. Combines especially well with support for concurrency.
- ResumableException/AbortRetryIgnore - exceptions that can be continued, hearkening back to the old days of DOS responses. When an exception occurs, one is given meaningful options for making progress and thus allowing for policy to be separated from the algorithm that has an error. One might abort (and clean up resources), retry, ignore (skip or use a default), etc. This applies, for example, in the middle of an algorithm that is interacting with the Database because the dataset is too large to SeparateIoFromCalculation. It also might apply in the middle of a big transaction where, halfway through it, there is suddenly a permissions problem, so one is given the exception with the continuation and the description of the missing permission is able to modify the environment to have the right permissions/certificates before retrying... even asking the user for the right to those permissions (policy again)... all without (necessarily) aborting the transaction.
- Explicit Security Model - a true integrated security model such that procedures can be prevented from executing that which they lack permission to execute. This author is partial to a PasswordCapabilityModel integrated with the ExplicitManagementOfImplicitContext, where the context holds certificates offering rights to perform certain actions (e.g. to manipulate a particular bit of state). This would be especially useful if integrated with the OperatingSystem (i.e. using the same sort of certificates in each).
- CompileTimeResolution - ability to perform certain resolutions at compile-time in order to configure the application to the environment in which it will be run (e.g. the databases it will access, the number of CPUs, etc.).
- ExtensibleProgrammingLanguage, plus enough PartialEvaluation to keep it equivalent to having written the code normally... thus obviating the need for most third-party AutomatedCodeGeneration and allowing one to remain within the language even for more complex tasks.
This list is turning into a general language feature wish-list. It was originally intended to make it easier for procedural to compete with the likes of OOP and FunctionalProgramming without becoming them.
[So... You'd also like to turn a bicycle into a car without an engine or four wheels?]
RE: "This list is turning into a general language feature wish-list. blah blah blah" -- Nested functions, multi-line text quoting, multiple returns, named parameters, and flexible data structures - i.e. everything you listed - are also general language feature wishes, buddy, so halt that whining. And all of the features named above certainly would help any procedural language compete with or surpass an OOP/FunctionalProgramming language that lacks them, and none of the features named above make the language 'become' functional or object oriented.
I'm not sure I entirely agree. OOP could use attributes instead of named parameters, for example. One could argue named parameters are not orthogonal enough with an OOP attributes. (Then again, one could also use associative arrays instead of named parameters, but there is also overlap between objects and associative arrays, especially in dynamic languages. The boundary between paradigms is fuzzy. But that's life.) -t
- Nested functions - A nested function automatically inherits a parent's scope. Very useful for reducing parameter complexity for functions that are closely tied. Pascal has these. An alternative for a dynamic language is to allow any routine to inherit scope of caller if a keyword/modifier is given.
- Modula-2 and Oberon has these as well (not surprisingly). GNU C has them as well, but they're almost never used. OTOH, I completely agree that greater exploitation of this would make coding much simpler. I really miss these features. It is such a shame that inner-procedures (as I believe NiklausWirth calls them) are such a pain in the @$$ to implement. Moreover, access to the parent scope's variables is less efficient, at least as they're implemented in {Pascal..Oberon}.
- This will make some gasp: bring back in-function GOSUB's from BASIC. GOSUB got a bad rap that is only deserved if misused, such as used in place of actual functions (which were not originally available).
- [GOSUB didn't support parameters. A GOSUB with parameters and local variables is effectively indistinguishable from an inner-procedure. Re the above "pain in the @$$ to implement", having implemented nested functions in a language, they were not especially difficult to implement and my implementation approach had a nice side-effect of easily supporting first-class functions, continuations and closures, though admittedly not with particularly stunning performance.]
- I find that one does not need parameter lists for closely-tied sub-functions in most cases anyhow. A gosub-like statement would be satisfactory. If you need complex parameter management, then just use regular functions. A hybrid may not really be worth it if it has high language complexity costs.
- {You won't encounter the problems with inner-procedures unless you also attempt to implement them under heavy access-efficiency constraints (esp. with intent to maintain the stack architecture while supporting recursive calls, which forbids downarg inner-procedure function passing... etc.) It isn't such a problem if you just go for the (often less efficient... albeit coefficiently, not algorithmically) FirstClassFunctions and chained, garbage-collected call contexts to support downarg function passing.}
- They are a pain in the rear to implement because you have to maintain the dynamic scoping in an otherwise lexically scoped language. Access to outer variables involve multiple dereferences from the inner procedure. Unless you're willing to use "super-promotion" (I think that's the term, where inner procedures are secretly "promoted" to outer procedures with implicit, like-named parameters as the outer procedure's namesakes), I can't see any other way of implementing them.
- [The usual mechanism is either displays (see AlternativeMicroprocessorDesign) or a CactusStack. Implementation to achieve acceptable performance (for some arbitrary value of "acceptable") is fairly straightforward. Achieving high performance, not so much...]
- {If the procedure isn't recursive and isn't used as a function pointer, you can actually maintain lexical scoping (the parameters from the context simply referencing below the call frame). For others, you can essentially use a hidden parameter pointing back to the frame of the original call context and use fixed offset from this frame pointer to reach the parameters from the inner context... which will work so long as you're going up the stack (uparg). Again, not too bad - no more costly than is access to an object attribute with it's hidden 'this' pointer, and extremely useful when it comes to passing functions into sorting algorithms and such. The real problem is when you want to either return a function pointer to an inner procedure (downarg) or pass one off as an asynchronous callback or anything similar, neither of which is feasible to do both safely and efficiently (since one could easily end up referring to a dead frame on the stack). For downarg, I've never seen a satisfactory solution other than just going to FirstClassFunctions or FunctorObjects, either of which will essentially demand GarbageCollection. Some languages, like DigitalMars's DeeLanguage, do both, using the optimized uparg solutions if it can prove that is how the inner procedure is used (and it often is, especially when programmers write code whilst aware of the optimization) and switching to FunctorObjects for the downarg. I'll note that use of CactusStack is aimed more at the downarg problem; in particular, CactusStack becomes that way because you've still got FirstClassFunctions and/or continuations living in the program that access a garbage-collected stack.}
- {'Displays' seems to me of very questionable optimization value; it supports only the uparg inner functions, and the hidden parameter solution there (usually hidden inside the function pointer) is already very good (a fixed-offset indirection from a register is almost free!). In addition to being good, hidden parameter solution is also more generic when comes time for an inner function pointer entering an uparg context (e.g. to a sort function) of another inner function (e.g. within the sort algorithm) where reliance on 'displays' may be confused and require saving/loading from the stack.}
- Multi-line text quoting - Useful for SQL and markup (XML, HTML) embedding.
- Python, at least, supports this using the """ delimiter.
- Multiple returns - Ability to return multiple values.
- Or, for that matter, the ability to return structured values at all.
- Most languages have the ability return arrays/maps these days. But they are awkward for simple stuff. They create too big a DiscontinuitySpike.
- Destructuring/pattern-matching assignment naturally leads to this, provided you either have tuples [as in most functional languages which have this] or allow heterogenously-typed lists [as in Python, the only dynamic language I can think of which has this but not general pattern-matching assignment]. But, wait, isn't that a FunctionalProgramming feature? Maybe this is the language-design equivalent of GreenspunsTenthRule??
If you really want to get away from object orientation, dump the idea of structures (classes without methods). Stop the TightCoupling? of the members of structures by no longer using structures. Remove structures (and classes) from your language. Find a way of relating the members of structures in some more fluid way than having them part of the same structure. Associative arrays come to mind as in Perl. Ontologies come to mind as in OWL.
-- JonGrover
Functions and pattern matching come to mind, as in HaskellLanguage.
Or step beyond even that, and embrace PrologLanguage.
{Sounds more like MergingMapsAndObjects to me. Anyhow, the goal is not to get away from OOP, but rather leverage procedural more. The need for OOP may drop with access to more powerful procedural features, but there are probably times and places where OOP is still the best tool for the job. Some kind of ControlTable-enabling features may also help so that complex criteria can be used to dispatch functions/events/methods. To borrow from SQL-isms, it could allow something like "RUN functionX(foo.x, bar.y) FROM foo, bar WHERE foo.id=bar.ref AND foo.z < 7 ORDER BY bar.fiz". It approaches the ability of PrologLanguage without the hard-to-predict info-chomping paths, and something relatively similar to common and well-known tools. }
{Yes, I would like to order a fizzy at the bar....*hic* :-}
See also: ItsTimeToDumpCeeSyntax
CategoryProgrammingLanguage, CategorySyntax
AugustZeroEight