On 7/28/2011 8:19 PM, David Barbour wrote:
On Thu, Jul 28, 2011 at 2:16 PM, BGB <cr88...@gmail.com
<mailto:cr88...@gmail.com>> wrote:
striving for simplicity can also help, but even simplicity can
have costs:
sometimes, simplicity in one place may lead to much higher complexity
somewhere else. [...]
it is better to try to find a simple way to handle issues, rather
than try to
sweep them under the carpet or try to push them somewhere else.
I like to call this the difference between 'simple' and 'simplistic'.
It is unfortunate that it is so easy to strive for the former and
achieve the latter.
* Simple is better than complex.
* Complex is better than complicated.
* Complicated is better than simplistic.
The key is that 'simple' must still capture the essential difficulty
and complexity of a problem. There really is a limit for 'as simple as
possible', and if you breach it you get 'simplistic', which shifts
uncaptured complexity unto each client of the model.
yeah. "simplistic" IMO would likely describe the JVM architecture at
around 1.0.
the design seemed clean and simple enough (well, except the class
libraries, where Sun seemed to really like their "huge number of
classes" / "huge number of API calls", in stark contrast to the
minimalism of the Java language and Java ByteCode...).
some time later, and a mountain of crud is built on top. why?... because
JBC could not extend cleanly or gracefully.
later, when I was working on my own implementation of the JVM (seemed
like a good idea at the time), I developed a very nifty hack to the
".class" format to allow more readily extending the file format.
some of my own bytecode formats (designed after all this petered out),
basically took the basic idea and ran with it, essentially discarding
the whole rest of the file format (the existence of any structures
besides the constant pool seemed unnecessary).
now, whether this is simple or simplistic, who knows?...
sadly, the contained bytecode itself is not quite so elegant (at the
moment it has 533 opcodes). it has also "evolved" over much of a decade
now, but has never really had an external file-format. in its abstract
sense, it is an RPN and blocks-based format, vaguely like PostScript
(stack machine with a dynamic/variant type-model, makes heavy use of
'mark', ...)
a close sibling/fork of this IL was used by the C compiler, but that
version made some ill-advised simplifications (damage caused by
initially dropping nested blocks, using right-to-left ordering for
ordinary function calls, but left-to-right for built-ins, ...).
We can conclude some interesting properties: First, you cannot achieve
'simplicity' without knowing your problem or requirements very
precisely. Second, the difference between simple and simplistic only
becomes visible for a model, framework, or API when it is shared and
scaled to multiple clients and use cases (this allows you to see
repetition of the uncaptured complexity).
I first made these observations in early 2004, and developed a
methodological approach to achieving simplicity:
(1) Take a set of requirements.
(2) Generate a model that /barely/ covers them, precisely as possible.
(This will be simplistic.)
(3) Explore the model with multiple use-cases, especially at large
scales. (Developer stories. Pseudocode.)
(4) Identify repetitions, boiler-plate, any stumbling blocks.
(5) Distill a new set of requirements. (Not monotonic.)
(6) Rinse, wash, repeat until I fail to make discernible progress for
a long while.
(7) At the end, generate a model that /barely/ overshoots the
requirements.
I generally start with a more "complex" description and tend to see what
can be shaved off without compromising its essential properties. doesn't
always work though, such as if requirements change or a dropped feature
comes back to bite one sometime later.
This methodology works on the simple principle: it's easier to
recognize 'simplistic' than 'not quite as simple as possible'. All you
need to do is scale the problem (in as many dimensions as possible)
and simplistic hops right out of the picture and slaps you in the face.
possible, but at times problems don't fully appear until one tries to
implement or test the idea, and one is left to discover that something
has gone terribly wrong.
By comparison, unnecessary complexity or power will lurk, invisible to
our preconceptions and perspectives - sometimes as a glass ceiling,
sometimes as an eroding force, sometimes as brittleness - but always
causing scalability issues that don't seem obvious. Most people who
live in the model won't even see there is a problem, just 'the way
things are', just Blub. The only way to recognize unnecessary power or
complexity is to find a simpler way.
IMO, glass-ceilings and brittleness are far more often a result of cruft
than of complexity in itself.
plain complexity will often lead to a straightforward solution, if
albeit an often similarly complex one.
OTOH, cruft (or kludges) will often crumble if poked too hard, or will
lead to otherwise unpredictable behavior or bugs.
IMO, the main form of cruft is a result of trying to side-step a usual
source of complexity: properly following APIs and maintaining
abstraction, and instead resorting to cheap hacks which work but depend
on code/behaviors/implementation details/... that they really shouldn't.
sadly, abstraction and modularity can be itself a big source of overall
system complexity.
So, when initially developing a model, it's better to start simplistic
and work towards simple. When you're done, at the very edges, add just
enough power, with constrained access, to cover the cases you did not
foresee (e.g. Turing-complete only at the toplevel, or only with a
special object capability). After all, complicated but sufficient /is/
better than simplistic or insufficient.
fair enough, if albeit I prefer to operate in the other direction.
I've been repeating this for 7 years now. My model was seeded
in 2003 October with the question: /"What would it take to build the
cyber-world envisioned in Neal Stephenson's Snow Crash?" /At that
time, I had no interest in language design, but that quickly changed
after distilling some requirements. I took a bunch of post-grad
courses related to language design, compilers, distributed systems,
and survivable networking. Of course, my original refinement model
didn't really account for inspiration on the way. I've since become
interested in command-and-control and data-fusion, which now has a
major influence on my model. A requirement discovered in 2010 March
led to my current programming model, Reactive Demand Programming,
which has been further refined: temporal semantics were added
initially to support precise multimedia synchronization in a
distributed system, my temporal semantics have been refined to support
anticipation (which is useful for ad-hoc coordination, smooth
animation, event detection), and my state model was refined twice to
support anticipation (via the temporal semantics) and live programming.
Snow Crash: "dot pattern from space -> brain-damage -> glitching avatar
-> biological virus" => "how does this work, exactly?..."
but, this book does hold some special status for me: I actually bothered
to read all of it.
I started out with language design and VM implementation some time
around 2000.
at the time I was using Guile, but was frustrated some with it. for
whatever reason, I skimmed over the source of it and several other
Scheme implementations, and threw together my own.
IIRC, my initial exposure to Scheme was IIRC by reading Euclid's Window
(I think this was it), IIRC because it mentioned Lisp and Scheme at one
point in the book, and at the time this seemed interesting (yes, in
middle and high-school I was probably a bit different than now, as I
actually liked math back then, ... this being before being repeatedly
"owned" by classes and Q-like teachers helped to ruin my general opinion
of the topic).
my first BGBScript implementation was in 2004, prompted by several factors:
my Scheme implementation had turned into an unmaintainable mess by this
point, so I dropped it;
I tried using a PostScript derived language as my primary scripting
language, and found this to be a terrible language to work in directly;
I recently had encountered JavaScript, and thought it was cool, so
wanted to have something similar for my own projects.
nevermind that this first implementation sucked (it was itself a big
pile of kludges), and from about 2007-2010 I had largely almost
forgotten it (it was "sort of on life support"). in 2010 interest in it
was renewed some due to initially unrelated developments leading to a
nifty new FFI.
I /had/ to develop a methodological approach to simplicity, because
the problem I so gleefully attacked is much, much bigger than I am.
(Still is. Besides developing RDP, I've also studied interactive
fiction, modular simulations, the accessibility issues for blind
access to the virtual world, the possibility of CSS-like transforms on
3D structures, and so on. I have a potentially powerful idea involving
multi-agent generative grammars for intelligent controlled creativity
and stability in a shared, federated world. I doubt I'll finish
/any/ of that on my own, except maybe the generative grammars bit.)
Most developers are clever, but lack perspective. Their eyes and noses
are close to a problem, focused on a local problem and
code-smells. When they build atop a /powerful/ substrate - such as OOP
or monads - composition and integration issues are opaque to them. A
common consequence is that their algorithms or DSLs are /simplistic/:
they work okay for /a specific/ program or example, but they are
difficult to integrate in a new context.
/Too much power with too little perspective - that is the problem./
/
/
I doubt "power" is the cause of this problem.
being simplistic or made from cruft or hacks are far more likely to be
their downfall.
Developers have recognized this problem, at least implicitly, and so
they build frameworks. A framework is an ad-hoc, slow, informally
specified, bug-ridden language that is, critically, more constrained
than full OOP. The idea is that we write code in the context of a
framework, and the constraints imposed on us help with integration.
Unfortunately, most frameworks are simplistic, built too close to a
problem, and do not integrate nicely with other systems and
frameworks. A common consequence is that developers spend more time
working around a framework, or adapting between frameworks, than
working within them.
above:
I regard this as "ivory tower VM design", the likely biggest example of
this strategy being the JVM, however, it is not unique to the JVM (most
VMs have it to a greater or lesser extend).
major properties:
typically a language-centric design ("language X will rule the world");
VM facilities are typically only really usable from within "language X";
typically a set of APIs which try to largely or completely wrap the
underlying system;
typically a poor FFI (and poor cross-language integration);
...
Still, the goal of frameworks is noble. Just, to make them work, we
must start with a large set of diverse problems across many domains,
and at enormous scales... millions of developers, integrating
thousands of DSLs or frameworks. We need a general framework
programming language, one that ensures a large number of compositional
properties - most critically, those useful for system integration.
This is, in a sense, what my RDP attempts to be. My pursuit of
/reactive/ programming stems from this requirement directly: if we
have reactivity, then abstracting or composing a framework is little
different than abstracting or composing any other object.
I don't think the problem actually requires anywhere near this level of
resources, rather it more requires FFIs capable of doing a bit of "heavy
lifting" needed to integrate disparate languages and technologies in a
relatively seamless and automatic manner.
a good portion of the problem then boils down to data-mining and heuristics.
source code/headers/... itself actually provides a good deal of
information for how to interface with it.
granted, practical limits still exist.
Some people question the value of simplicity. They think complicated
is 'good enough'. But these people don't understand that simplicity
opens doors that we never realized were closed, raises glass ceilings
we didn't realize we were struggling against, and removes performance
barriers that we wrongly assumed to be a natural part of our
environment (wow! who knew there was a racetrack under all this
debris?). A subtle qualitative difference in our abstractions can make
huge quantitative and qualitative differences in our emergent
systems. Pursuit of simplicity, at scale, tells us just how
ineffective and unscalable our systems are today... and offers great
hope and optimism for tomorrow.
possibly, but there is still a lot that can be done without requiring
fundamental changes.
typically though, one may parse code, and then compile it to a
stack-machine based IL, as personally I have had the most luck working
with stack-machines. IME, stack machines are fairly easy to reason about
from software, so I have had best luck with things like
temporaries/register-allocation/type-analysis/value-flow/... working in
terms of the stack.
in this sense, rather than seeing the stack as a 2D graph
(stack-location and time), it can be seen as a 1D array of immutable
temporaries (every value that ever was on the stack within a given
region of code, which can be mapped out statically regardless of any
internal control-flow/...).
note that stack items are immutable, because one can't actually modify a
value on the stack. they can only pop it off (which discards the
immediate reference to the value), or replace it with a new value (which
has a new identity, type, and storage assignment, despite overlapping in
terms of its stack-index). also, one doesn't need a forward scan to
determine whether or not any future references to a given value may
exist (once it is popped, its effective lifetime has ended, and any
assigned physical storage can be released for reuse).
...
but, sadly, RPN is not "in vogue" in the same way as TAC + SSA...
so, one can try to argue that it is an "inferior" technology in this
sense, but oh well...
_______________________________________________
fonc mailing list
fonc@vpri.org
http://vpri.org/mailman/listinfo/fonc