This note advocates approaching computer-based product development as 2 issues
This note examines the issue of performance.
“Performance” can be divided, broadly, into two categories.
- Performance during development.
- Performance in the final app.
The goals of 1 & 2 are vastly different.
In fact the end-user is a different person in both cases.
In (1), we need performance for development turn-around.
In (2) we want performance of the final app. In other fields, this is called Production
There is no need to use the same methods (i.e. the same language) for (1) and (2).
Most modern languages aim only for (2) and throw away many considerations for (1).
IMO, there is a confusion about the utility of dynamic vs. static languages.
Dynamic languages - generally - provide better debuggers. Static languages - generally - throw out debugging in lieue of faster execution and smaller executables.
Attempting to solve both problems with only one language makes the language design problem harder than necessary1.
(1) Performance For The Developer
In (1) we target performance for the developer.
Developer turn-around is the main issue.
The machine (aka computer) should be used to help the developer.
- elide details to allow better expression of the design
- elide details to allow deeper thinking about the design
- elide details to communicate designs better between team-members
- provide a rich syntax (e.g. text plus diagrams)
- garbage collection
- strong type analysis aimed at DI2
- books: “Design of Everyday Things”, “Humane Interface”
- dynamic languages3 and REPLs
- automatic inlining & macros (lisp-ish macros)
Development turn-around time can be improved by reducing inconveniences.
Less inconvenience might be provided by:
- declaration after use
- multiple syntaxes
- syntax checking
- parameter counting
- type checking
- lots of fundamental data types (Rebol and ASON)
Declaration After Use
Declaration before use is an aid to compiler development and optimization.
In the 1950’s, it seemed to be “more efficient” to build one-pass compilers.
The onus, though, has been placed on programmers to declare entities before they are used in the code.
This affects readability (for human developers) - the “most important” aspects of the design appear at the bottom of a file of code instead of at the top.
See Appendix “Two Syntaxes for Each GPL”.
There exists a tension between language sytax and “syntactic noise”.
On one hand, verbose syntax can be easily checked by automated means (i.e. compiler).
On the other hand, verbose syntax makes it harder to “get the big picture” for humans, when maintaining code.
This issue is discussed in the article “Two Syntaxes For Each GPL”.
To summarize, it is possible to use PEG technologies to provide more than one syntax for any given language, thereby achieving “the best of both worlds” (a verbose syntax that can be machine-checked, and a concise syntax to aid human understanding).
A seemingly small detail has come out of more involved research on type checking and type inferencing - simply checking the number of parameters to a function.
This detail is much like the “if … end if” detail in language syntax design. It seems trivial but can catch many simple errors effectively and automatically.
Language designers learned this lesson early, but didn’t state it explicitly.
Type checking was invented as an optimization.
Type checking allowed one to strip type conversion code from BASIC interpreters (making them run faster).
Type checking and type inferencing has grown, beyond optimization, into a useful tool for thinking about program design.
Many languages inconvenience the programmer by making the programmer explicitly declare types.
This notational inconvenience can be ameliorated by using PEG-based preprocessors tuned to the problem-at-hand (or, by allowing declaration-after-use).
Many Fundamental Types
Sassenrath (inventor of Rebol, which led to the invention of JSON) showed that a lot or practical results can be accomplished by providing a rich set of fundamental types, without requiring the programmer to define them explicitly as classes.
Sassenrath is refining his ideas as ASON (see Appendix)4.
Inlining and Macros
Inlining allows programmers to express code in functional form, and the compiler generates “more efficient” code than pure function calls.
It used to be the case that spotting opportunities for inlining was left up to programmers, e.g. programmers had to insert keywords to mark inlinable functions.
Lisp macros represent inlinable-functions-on-steroids, but, still defined by the programmers.
Compiler technology has developed ways to recognize inlinable functions and to optimize code sequences.
Clearly, one needs to know what one’s compiler can and can’t do.
If the compiler implements automatic inlining, most code should be written as (inlinable) functions.
Lisp macros differ from C macros in that Lisp macros allow arbitrary routines to be called at compile-time and to generate code to be fed to the compiler.
C macros begin at the other end of the spectrum. C macros are more like Find-and-Replace operations normally found in editors.
C macros define a different “language” for writing macros. Lisp macros, OTOH, use the same Lisp language for writing macros.
Ohm (PEG) can provide lisp-like macro preprocessing for any text-based language.
The m4 macro processor5 was designed to provide macro preprocessing that is more powerful than search-and-replace macros. Tools like sed and awk, etc., might be used to pre-process code.
Languages that have built-in REGEXP can be used to write macro-like pre-processors. IMO, PEG is better than REGEXP.
(2) Performance For The Consumer
In (2) we target performance for the consumer.
Cost of the final product.
- memory footprint
- cheapest final hardware (e.g. can the app run on a Raspberry Pi, a phone, or, only on the latest hardware?)6
Various techniques aid in Production Engineering, like
- strong type analysis aimed at product performance (allows stripping type checking code from the final product)
- O(n) analysis
- C, Rust, C++, etc, etc.
- memory sharing
- syntactic trade-offs to aid compilation (aka static languages)
Time To Delivery
Q: What’s faster? Forcing the use of only one language for product realization, or, using more than one language?
Some say that Lisp aids in creating products faster, some say that Rust should be used.7
Architecture, Engineering, Implementation
- high-level details
- extract requirements from end-users, codify requirements
Cost-benefit analysis, refactor the Software Architecture to reduce final cost (Q: along which dimension(s)?)
Often mis-named engineering (Software Engineering is not Coding, Implementation is Coding).
Users of Computers
The programming community addresses the needs of business users, with products like Windows, MacOSX, WYSIWYG word processors, calendars, etc, etc.
Addressing the needs of programing development is mostly stuck in the 1950’s - text languages, text editors, GPLs for Software Production Engineering (but no popular languages for Software Architecture).
How? Using Current Languages
Most current languages are implementation languages, e.g. C++, Rust, Haskell, etc.
GPL (General purpose Programming Languages) are not very general. They are - typically - aimed mostly at implementation.
E.G. any language that requires the programmer to define a data structure is an implementation language (i.e. implementation of the data structure (instead of using a handle to data which is defined elsewhere)).
E.G. any language that supplies low-level operators, like
+, is an implementation language8.
Why are some developers “better” than other developers? Discipline, structuring of their designs and implementations. Similarly, some people could write structured assembly programs before HLLs became popular.
Use shell pipelines and *sh to construct MVPs.
Note that mapping *sh-based programs to GPL-based programs is not straight-forward, since shell commands are asynchronous and GPLs are mostly synchronous9. See Appendix (Call/Return Spaghetti) for ideas on how to perform such mappings.
PEG parsing10 provides a way to create syntax skins over existing languages. Write PEGlets (I call them SCNs) that capture DI and emit different code/languages during development and production.
Creating different languages (better yet: creating IDEs), makes it possible to fine-tune the different languages while eschewing language bloat.
The Handmade Network’s manifesto opposes language bloat. The above begins to address at least some of the issues alluded to in that manifesto.
Appendix - PEG Applied To WASM
POC for PEG-to-WASM…
Appendix - PEG Applied to Transpilation
PEG vs Other Pattern Matchers
(see TOC for further discussion)
Appendix - PEG Applied to PEG
Appendix - Two Syntaxes For Each GPL
Appendix - Software Development Roles
Appendix - Call Return Spaghetti
Breaking CALL/RETURN into separate parts.
This article does not directly show how to build concurrent programs, but, if you already know about queues and anonymous functions, it should be a short hop to implementation of true concurrency.
[IMO: Concurrency is a paradigm, not a bag bolted onto the side of a language using thread libraries.11]
Appendix - Handmade Manifesto
Appendix - Ohm-JS
Appendix - My Goals
Appendix - Worlds
Appendix - ASON
Appendix - Book: Design of Everyday Things
Appendix - Book: The Humane Interface
Appendix - References
We need an IDE that allows us to use both kinds of languages. Q: Do we need a seamless transition between both types of development? Q: How much is that worth? ↩
DI means Design Intent ↩
Every language can be interpreted. The emphasis has been on twisting syntax to allow easier compilation. ↩
Again, Ohm (a better PEG) could be used to graft these ideas onto existing languages. ↩
found on *nix. ↩
I advocate that Optimization be considered and programmed separately, instead of being tangled up with Design. ↩
IMO: I would not handcuff myself with Rust. I advocate (1) creating a Design (i.e. MVP), then (2) optimizing the hell out of the very few places that actually need it. I want a language that protects me from silly mistakes during (1) and then I want a language that gets out of my way during (2). If I never have to spend effort on (2), all the better. The key is to realize that you can’t do (1) and (2) at the same time, one or the other suffers. The biggest “win”, IMO, is to allow oneself to create (1) in a concurrent paradigm. ↩
The only dataless programming language that I know of is S/SL (not to be confused with SSL). ↩
Note that a piepine of functions is not the same as a *nix pipeline ↩
My current favorite PEG language is Ohm-JS. The question of “Why?” is addressed in other notes (see TOC). ↩