Programming language malleability, etc.
cpcogan — 2004-12-30 21:48:11
I found X's idea of having function results go to the queue or to the
stack depending on their data-or-function type interesting. What I
liked about it was the idea of the underlying system doing something
automatically that I would have to do "manually" otherwise.
This is in fact a major design goal of mine: Making the way things
happen be what we want to happen without having to explicitly specify
it, and yet doing so without a table of rules and such. That is, the
idea is to make the system "just happen" to work in such a way that
it frequently does what we want it to just because that's the way the
pieced of the "mechanism" happen to work together.
Achieving this requires carefully choosing our primaries. It's like a
mathematical system where we have so carefully chosen the postulates
that the resulting system of theorems just happens to be one that is
useful to us. Some games are this way, too (Go and Chess come to
mind, especially Go, though there are places in both games where the
underlying simplicity has been corrupted to make them more playable).
One of the things about a good concatenative language that I like is
that we may *never* need to "corrupt" the underlying mathematical
structure to make them workable. By finding suitable alternatives to
loops and variables and such, we may finally have languages in which
we can have *BOTH* genuinely practical and mathematically pure.
This is the sort of thing that can happen when the foundational
issues of a discipline are adequately dealt with. Spending our days
constructing loops and If-then-elseif-then-elseIf-etc constructs is
silly, when you think about it (and annoying as well). That's exactly
the sort of things *computers* are supposed to be doing. And yet,
here I sit, day in an day out, writing loops and If-then-elses and
such in VB .NET.
This brings me to: Malleability. Prototyping has been around for a
long time, even in the software development business, but most
programming languages do not lend themselves well to a prototyping
approach, largely because of the requirements of all the language
constructs that make modifying a program in useful ways much more
difficult than it really should be, most of the time.
Spoken and written language have the malleability that programming
languages should strive for (or at least toward). Our own programming
languages have a lot of this malleability that's missing from most
conventional languages.
Why prototype? Because, if you can work out the entire design before
coding and testing anything, then you are doing something that's too
routine to be interesting, and possibly too routine even to be useful.
We need to design software by thinking and programming and testing,
if we are doing something at all novel, because that's how we find
out what a near-best or at least good-enough way to do it is. The
structures we build in software are too rich in their working-out to
be handled well by the static-engineering approach.
And, as soon as we *do* work something out so well that we can handle
it statically, it becomes just another component in some much larger
structure that still needs experimenting to work out fully.
Developing new software is a little like scientific theory building;
we formulate a hypothesis, work out a prediction (a software
implementation to test) and then we test the hypothesis (run the
software) and consider whether the results falsify the hypothesis
(show that the software is workable).
Of course, paradoxically, languages like Joy give us the *best*
chance of doing things by static-engineering methods, because getting
rid of loops and variables is a major step towards making them have a
static structure that reflects the process we are trying to
implement. That is, instead of thinking of something as a loop (for
example), we can think of it as a black box that requires certain
inputs and provides certain outputs, but we don't have to know what
it really does inside.
In a sense, the problem with loops is that they are black boxes that
have been open up and their guts strewn over many lines of code, with
all the gory details taking up mental and source-code space. I want
to say, "That's really ugly. Put that into a black box where it
belongs."
Ordinary languages allow use to make a black box for *specific* loops
(etc.) of course, but they don't allow us to make a *single* black
box for all loops that have certain abstract characteristics (such as
performing an arbitrary piece of code N times). For each arbitrary
piece of code, I'm very likely going to have to make another such
black box. This is because we can't pass code as parameters in
ordinary languages; the code has to be hard-coded into each loop or
other such construct. Ugh.
The greater hiding of "guts" allowed by combinators actually
*reduces* the need for the malleability I spoke of earlier, because
it makes these things into discrete "building-block" units (along
with ordinary functions like calculating sqaure roots and such). As
discrete building blocks, they are much easier to manage
conceptually, so we are able to mentally grasp "larger" problems with
the same work, thus enabling us to do more of the "hypothesis-
testing" in our minds, thereby reducing the need for malleability. Of
course, in practice, we want both, so we can tackle ever more-
interesting problems successfully at less cost in drudgery and time.
Conclusion: Languages like Joy give us two major benefits not found
in the mainstream languages: Great malleability so we can easily try
programming hypotheses out *AND* much greater manageability by static-
engineering analysis methods, so that the malleability may in some
cases be much less *needed.*
--Chris