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

Reply via email to