On Fri, Jul 29, 2011 at 3:12 AM, BGB <cr88...@gmail.com> wrote: > 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. >
That strategy does not seem very wise. Many systems are minimal (you can't remove a feature without breaking them) but not simple - Turing tarpits, for example. Thus, if you don't start with a superset of a simple solution, you won't find a simple solution by shaving things away. Given N features, there are 2^N subsets. Thus, if you don't start with a 'close' superset of a simple solution, your search-space will be much too large. You need to know your essential properties, but you only learn more about them when you've made your solution too simplistic and get 'bitten' sometime later. It's much more efficient start simplistic and accept your bites early. > > 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. > Sure. I've gone through five implementation cycles in eight years. I learned a lot from them. I've rejected transactions, promises, connectionless message-passing, and other partial solutions based on realizations during implementation. My 2009 attempts at multi-cast, resilience, and mobility - in the context of legacy adapters for common capabilities (like Internet access) - led (through intermediate models) to the development of RDP. But, often a problem hops out *before* I implement and test. My set of developer stories, use cases, and pseudo-code tests grows with each failure or inspiration. They help me develop idioms and abstractions and get a real feel for a new model. Discovering a flaw or some undesired property at this time is orders of magnitude more efficient than waiting for a full implementation and real-world testing. IMO, glass-ceilings and brittleness are far more often a result of cruft > than of complexity in itself. > Complexity - i.e. being built of many, small, independent parts - can be acceptable, when the emergent behavior is predictable. Unfortunately, complexity often leads to unpredictable chaos and complication, especially at scale. Consider symptoms such as convoying, priority inversion, heisenbugs, deadlock, livelock, flash crowds, confused deputy, resource thrashing, packet floods, inconsistency, path dependence, state after partial failure, dead links, tangled dependency forests, and so on. Complexity easily becomes complicated - i.e. difficult to predict, or explain. We can counter this by introducing artificial barriers at scale, rendering the model less scalable but more predictable. But those become the same 'glass ceilings' I mentioned earlier. We imprison ourselves. I've learned to counter complexity by focusing on *compositional properties*. With 'composition', we have a standard set of operators for combining elements, and the result of composition can be further composed by the same operators. (That is, we have something akin to algebraic closure.) A property is compositional if, and only if, we can reason about that property in the result from knowing it for each operand. Critically, this means we cannot peek inside each operand. For example: progress is *not* compositional in a system with mutexes, because we can combine two deadlock-free subprograms but cannot say whether the resulting program deadlocks. I'll note that common models of OOP are notoriously non-compositional. There are no *standard* operators for combining two or more objects into a new object, and very few properties we can reason about. Actors model is not compositional because we cannot treat a configuration of actors the same as one actor due to races between messages. For composable paradigms (of varying quality), I would direct you to functional programming, arrows, monads, FRP, dataflow, concatenative languages, grammars, and logic programming. Compositional properties allow us to achieve a sort of 'simplicity at scale', i.e. the ability to (a) reason about and (b) integrate with existing systems, without knowing deep operational details. To be 'scalable' requires quite a few compositional properties that are useful at scale (*progress* is only one of them). And to be 'open' - i.e. support runtime extension, federated systems, independent maintenance and development, security - requires even more compositional properties. With Reactive Demand Programming, I achieve the following compositional properties: idempotence, commutativity, concurrency, eventual consistency, useful classes of optimizability (continuous compilation and specialization of slow changing behaviors via asynchronous reactivity; duplicate elimination and ad-hoc multi-cast content distribution via commutativity and idempotence), predicative space (bounded-space per client), predicative time (real-time, duration coupling), performance isolation (up to resource limits), resource management (resource acquisition is demand - RAID, implicit release via duration coupling), resource scheduling (via anticipation), support for ad-hoc coordination of independent systems (via anticipation and reactive propagation), visible security (object capability model, automatic revocation based on reactivity), resilience, and predicative failure modes (all failures reduce to disruption, which can be handled elegantly via disruption tolerance patterns and code distribution). And my *implementation* of RDP supports even more than that. I.e. I use vats to support parallelism, batch-processing, global snapshot consistency, local determinism (up to a small group of in-process vats). I have ideas on how to support these properties in an RDP language. Eventual consistency makes a nice safety net, but I don't like to depend on it. > > Snow Crash: "dot pattern from space -> brain-damage > Ah, yes, that wasn't the bit I wanted to create from Snow Crash. Just the vision of the cyber-world, with user agents and a multi-company federated but contiguous space. > > 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. > My start in language design is similar. Guile was one of the implementations I studied, though I did not use it. > > *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. > Power, in the absence of perspective, leads humans to develop cruft. Hacks are how we compose cruft. Self-discipline and best practices are ways to 'tame' the use of power, when we have too much of it. Unfortunately, these are not practices that scale. Self discipline won't help when your libraries suck because *someone else* was not disciplined. We cannot universally fix perspective. Humans *need* the ability to specialize - it takes ten years of passionate education to gain a tiny fraction of useful human knowledge today. Those numbers will only further diverge over time, and filtering the useful knowledge from speculation and drivel will only become more difficult. It is too easy to miss lessons of the past. That problem will only get worse. Between power and perspective, power is the problem we can most effectively tame by technological means. > > 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). > The JVM was not designed in an ivory tower, BGB. Further, it was pressured and pushed by Sun's marketing before its developers thought it ready. JVM was a classic example of 'worse is better, get on base first and patch up later'. For ivory tower languages, I suggest you review Haskell, Oz/Mozart, Maude, Coq, Lustre, Charity, Bloom, Mathematica, and Scheme. I'm certain you could learn a lot from them. People who use "ivory tower" as a snub offer the impression of being close-minded and prejudicial. For impressions sake alone, you might want to work on that. > 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. > I agree that we can automate a lot more of FFI/foreign-service integration, and that doing so is a worthy goal. But for integration to be 'seamless' is much more difficult. I don't expect *seamless* integration even with a common programming model, due to variation and mismatch in data models. (Reactive and RESTful help, but there are fundamental ontology issues.) Foreign service integration is faced with many extra challenges: different concurrency and concurrency-control models, different evaluation-properties (strict, lazy, parallel, beta-reduction), different type systems, different error-propagation models, different memory management models, different data and control-flow models, different temporal and spatial properties, different distribution models, and on and on and on. Only when two languages are conceptually 'close' to one another (e.g. two procedural languages) is integration typically effective. I'll repeat that: effective integration requires that the languages share a lot in common - i.e. a common basis, foundation, substrate. All that aside, 'the problem' I was discussing seems to be independent of foreign services integration: today, we cannot effectively compose, integrate, and scale frameworks, DSLs, and applications developed *in the same* programming model or language. Even *without* the extra complications of foreign service integration, there is a major problem that needs solving. On a side note, I have spent a lot of time developing some excellent approaches for foreign-service integration. I've rejected use of a 'Foreign *Function* Interface' in favor of a plug-in extensible runtime. Each 'plugin' publishes modules and capabilities to the runtime, along with metadata to distinguish them. These are made available to application developers as objects or modules within the language. For the runtime itself, I supported built-ins, dynamic plugins, and separate executables (via Unix or TCP socket). Applications are also able to register their own caps as modules, and thus provide services.. The resulting model is much close in feel to how browsers use plugins, how web-serves use CGI, or how service-oriented architectures locate dependencies, but has the advantages of being open, extensible, flexible, distributed, securable, resilient (i.e. allowing fallback services), relatively easy to debug, language agnostic, and supporting a continuous transition strategy for slow absorption of foreign services. My original design for this was developed in 2008 and mostly implemented in C++ in 2009, but I've since transitioned to Haskell and will need to start from scratch. That's okay, it should be much easier the second time. Parts of my implementation, especially the 'vat' model, are already designed with this integration in mind. So I have not ignored the foreign service integration problem. I just haven't considered it a difficult or interesting problem for a few years now. > there is still a lot that can be done without requiring fundamental > changes. > I agree. Problem is, many of the more interesting things I want to do would be complicated, inefficient, unsafe, or insecure with our current foundations. > > 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. > The very idea of 'control-flow' is difficult to reason about - especially once you add a dash of concurrency, parallelism, reactivity, inversion of control, or heterogeneous memory (FPGA, CUDA, distributed, etc.). Stack machines are quite *simplistic* in the grand scheme of things. Seriously, we've been building some massive amounts of cruft around these stack machines for decades now. If you have a language that implements without complication on a stack machine, I hypothesize that your language is just as simplistic.
_______________________________________________ fonc mailing list fonc@vpri.org http://vpri.org/mailman/listinfo/fonc