In January of 2003, we published an interview with virtual reality pioneer Jaron Lanier, who argued that, given the near impossibility of writing large bug-free programs, a new programming paradigm was needed. In February of 2004, Sun Microsystems principal architect Victoria Livschitz responded with her own views on the crisis in software, focusing on problems related to the counterintuitiveness of modern programming paradigms. She suggested ways in which the complexity of software development can be addressed in next-generation programming languages. Since then, she has filled in some details about what such a language, which she calls Metaphors, might look like. Livschitz grew up in the Ukraine and Lithuania, two former republics of the USSR, where she competed in chess and mathematical Olympiads at the national level. She studied applied mathematics at Kharkov University in the Ukraine before coming to the United States, where she subsequently received a degree in computer science from Case Western Reserve University. After a four-year stint at the Ford Motor company, she came to Sun in 1997, where she has served as principal architect on several high-profile projects for major financial and automotive companies. She currently serves as a principal architect at Sun and as an adjunct staff member at Sun Microsystems Research Labs. We caught up with her to get her most recent thoughts on complexity, intuitiveness, her new Metaphors language, and the future of software.
The overall scale and complexity of software systems is increasing. Individual projects can't develop entire solutions themselves, so the pieces must come from different places. Enter design by contract, a powerful idea taken for granted in our industry. Components, loose coupling, and standardization of interfaces become the basis of software development ecology.
But what if the energy to develop and support these contracts is overwhelming? A system that consists of just five standard components, each available in four commercial implementations, produces up to 1024 configurations to test, certify, and support. How does this scale out? According to a distinguished engineer from IBM, a recent internal study revealed that only about one in three dollars that IBM spends on software product development goes toward new features. The other two-thirds are spent on non-value-added integration costs. IBM is no less efficient in software development than the rest of the industry. Mathematics is hard to argue with. The ratio of dollars spent on new features to dollars spent on certification and support goes down fast as the number of standard interfaces goes up. Two-thirds of non-value-added costs is already hard to swallow. What if that becomes four-fifths or nine-tenths? A number of senior software people know that we are in deep trouble and that we don't have a get-well plan, but it's not profitable to talk too much about it -- except as a sell point for the new miracle product of the day.
The problem is that modern software development is no longer mostly about programming. My wild guess is that something like 90 percent of software development happens outside of programming languages. Dozens of nonprogramming disciplines, from information security to release engineering to program management, define modern software development. Each discipline employs different principles, methods, and tools. Each requires specialized skills and educational backgrounds. A specialist uses a microscope to zoom in on one aspect of the system. Microscopes don't show cracks and gaps in the structural design of the overall system or hidden dependencies between subsystems. Where are our binoculars? Who provides the aerial surveillance of the systemic concerns of the systems we build? Consider a typical web service-based application running in a datacenter of a Fortune 500 company. When a failure occurs at runtime, is it the web service, the application server, the cluster, the service provisioning software, the monitoring tool, or the operating system that must react to the situation? In most cases, the response must be carefully coordinated across all layers, with each technology doing no more and no less than necessary. Which means that either (a) there is an overall system designer who assures the perfect separation of concerns between the layers, or (b) application developers and systems administrators must each thoroughly understand the application and its hosting environment and be able to reason about complex interactions between them. Neither is true in practice or even theoretically possible in the large-scale open-systems world. Now back to the question of the role of programming languages. As Alan Perlis put it, "A good programming language is a conceptual universe for thinking about programming." What we need is not just another programming language but a conceptual universe for thinking about software development, embodied in a kind of language that is new to computer science -- a language that will bridge the gap between programming and software development at large. The Metaphors Language -- A Way out of the Crisis
Let me pick the runtime characteristics first. Here, Metaphors borrows many ideas from the Java language, Java Platform, Enterprise Edition (Java EE) architecture, Jini technology, and the JXTA project. I assume that local runtime environments for programs would be provided by a virtual machine (VM). However, I'd like to go much further down the path of giving the local virtual machine (LVM) the ability to act as a container for managed application code. Imagine a virtual machine and distributed runtime environment that in principle subsume the functionality of the application containers and middleware. Distributed properties of applications will emerge through the collaboration between many LVMs that host parts of the same distributed application. Conceptually speaking, the execution of distributed applications is the responsibility of the distributed runtime core (DRC) that comprises a peer-to-peer network of LVMs. Let's say a developer will specify in a program that a car has four wheels. At runtime, the executable for each wheel may be loaded into any available LVM at the discretion of the DRC. The interactions between the wheels and the rest of the car will be brokered appropriately by the DRC and LVMs without explicit control by the developer or network administrator. The exact location of each wheel at any point in time is not deterministic but emergent from a combination of factors, including dynamic workload on LVMs, security and management policy of DRC, or configuration of devices that are hosting LVMs. Eliminating Middleware?
Collapsing LVM with containers and middleware should make the development of distributed applications a much simpler process. In one way, I see Metaphors not so much as a successor to the Java language but rather as a successor to complicated, disjointed, and proprietary middleware stacks. Principal Features of Metaphors
Let's talk about contextual and computational programming -- and why it's important to create a formal framework to distinguish them. A functional behavior of the application cannot be expressed without a contextual model for the application domain. For example, in order to codify a simple business rule, say, "Transfer X amount of dollars from Y account to Z account," there has to be a formal definition of the concepts of account, transfer, dollars, and amount. Once the context is set, one can write down the proper algorithm for fund-transfer function. The context, on the other hand, cannot be defined in isolation from the intended functionality of the application. As you can see, two sharply distinct intellectual activities are competing for the developer's attention. One aims to define a context for the application's functionality, and the other codifies the functional rules of the application's behavior within a given context. I refer to these as contextual programming and computational programming. Traditional computer science has always been consumed with computational programming, while contextual programming has been largely ignored until the advent of object-oriented programming (OOP). OOP established the separation of the analysis and design stages of the project (mostly concerned with contextual programming) from the implementation stage (mostly concerned with computational programming). OOP also introduced several convenient built-in context-building facilities such as classes, interfaces, and abstract data types. However, the explicit difference between contextual and computational programming is not supported in object-oriented languages. Instead, OOP relies on methodologies, external tools, and special-purpose languages -- most notably UML -- to address contextual programming outside of the programming language. Subsequently, the separation of context from computation in modern software development is based on the timing of the project's life cycle. This is a good idea wrongly framed. Software development is a continuous, iterative, and evolutionary activity. Neither context nor computation comes before the other. Both are organically related and belong to the same code base. Overcoming Counterintuitiveness
These developers are engaged in the process of analyzing and designing complex adaptive systems, in all their splendor and complexity. Certain questions will arise: What are the elements of this system? How are these elements organized? What kind of relationships exist between the elements? How do elements communicate? What rules are governing the behavior of each element? How does the system change over time? What processes are governing that change? How does this system interact with other systems? And so on. The developers need formal yet intuitive tools to describe general-purpose systems. I envision a set of core system-level constructs built into the language, designed to make the task of the formal codification of systems a far more intuitive process, as compared to programming in object-oriented languages. A partial list of good candidates for core constructs includes entity, process, organizational principle, condition, relationship, event, and rule. Several such constructs will be supported directly in the programming language as "primitive" metaphors. User-defined metaphors can be created incrementally from primitive metaphors using a formal extensibility framework supported by the language. The Executable Entity
It seems important to introduce a concept of executable entity, decoupled from the metaphorical constructs of contextual programming. Of course, executable entities may need to be generated from the contextual constructs. Something similar is going on in OOP, where executable entities (object instances) are generated from logical entities (classes). My executable entities, though, are going to be very different from conventional object instances that are sitting on a heap or a stack. The world around us that we attempt to simulate, automate, and control via software is profoundly autonomous and asynchronous. When an apple falls from the tree and hits the ground, the phenomenon can be observed as a sequential process, but it cannot be understood as such. The sequence of events is accidental, an emergent property of the system tree-apple-ground. An apple falls autonomously when the force of gravity overcomes the force of the apple's attachment to the tree branch. The tree and the ground respond to the force applied to them by the falling apple with counterforce, also autonomously.
Unfortunately, modern programming is profoundly passive and sequential. Execution starts with some bootstrapping mechanism, like Building concurrency on top of a sequential automaton is a bit like trying to explain quantum physics as an extension of Newton's laws. Both are probably better done the other way around. I'd rather assume that everything is autonomous and asynchronous. Application runtime consists of autonomous executable entities, or agents, as some would call them. These agents interact with their environment and each other asynchronously, respond to external stimuli, and initiate actions out of "free will." They may exert a force or be subjected to a force field, which basically means that their ability to choose their actions will be constrained by the circumstances. One interesting technical challenge is to devise a program execution model that will be able to efficiently support thousands -- if not millions -- of autonomous executable entities that are active at all times, even though truly interesting events happen to them only once every millionth or billionth cycle. Formal Extensibility Framework
Classic inheritance works that way, and it proved to be quite useful for reuse, both on the interface and implementation side of things. Why not go further? For example, consider reverse inheritance -- the ability to define a new entity as a generalization of an existing one. A In principle, I see three general-purpose reuse mechanisms that can be formally supported by the grammar of the language. The first is derivation, which specifies the rules of creation of a new metaphor by changing properties of the existing metaphor. The second is composition, which specifies the rules for creation of a new metaphor as an aggregate of several existing ones -- or the direct opposite of that, the decomposition of an existing metaphor into several metaphors. The third is adaptation, which specifies the rules of lightweight "morphing" protocol that can be used to adapt a metaphor developed for one domain to the needs of another domain, if the two are properly isomorphic -- by which I mean that they are either identical or similar in form, shape, or structure. The Process Metaphor
I'd like to make process one of the core contextual abstractions, thus allowing for abstract declaration and definition of processes, gradual refinement of process definition over time via the extensibility framework, and concrete implementation of process logic in an executable entity. In a way, predefined process is the opposite of emergent behavior. Processes themselves can be the products of emergent behavior, of course. The point is that the "process" abstraction can be used to bring algorithmic sequencing into the world of autonomous and asynchronous executable entities. Let me illustrate one aspect of what the Metaphors language should be able to offer developers using the process metaphor. Let's say a designer of a next-generation file system creates a metaphor to capture a process flow for reading or writing to a file, codified into a three-step algorithm:
Imagine also another three-step process metaphor used by a designer of a virtual machine for management of short-lived, transient objects, codified as
In a certain sense, both algorithms seem isomorphic. It is useful to be able to see that and draw conclusions based on that observation. Suppose the designers of the file-access algorithm later notice that they have missed one important step in the process. So they derive a new version of the algorithm:
How does this discovery affect the designers of the virtual machine? At the very least, it should give them food for thought. Did they make the same mistake, or was the perceived isomorphism misleading? Upon careful consideration, they find that the parallel between the long-lived file handler and the short-lived object reference is not complete. However, they are currently working on the implementation of long-lived persistent objects. There, they indeed can reuse the file-access algorithm through an isomorphic transformation, which results in
Programming languages should facilitate this kind of reasoning. Why "Metaphors"?
Take almost any computer term -- programming language, virtual machine, register, memory cell, stack, queue, tree -- and you'll find a metaphor for some physical entity or process or well-understood previously defined metaphor. If programmers naturally think in terms of metaphors and recursively build higher-level metaphors from lower-level ones, then that should be the model for the software creation process supported by the programming language. Eventually, I hope to facilitate a style of software development in which one can say, in a programming statement, "The contextual model of this application is just like a contextual model of that application, except..." and then define exactly how the two are different. Addressing the Crisis
I believe it's time to step back and consider the first 50 years of computing as one gigantic prototype. If we had the courage to start over, we could build a new software creation and execution model to address the software engineering challenges of the 21st century, such as distributed processing, autonomic computing, and software evolution -- natively, within one conceptual universe. The programming language is a great place to start. Will we be starting from scratch? Well, yes and no. The changes I am talking about require a very deep cut into the programming, execution, memory management, and threading models -- just about everything that makes up a programming language. On the other hand, collectively, we are armed with decades of experience building virtual machines, functional languages, middleware, clusters, service containers, databases, operating systems, communication networks, etc. We also can and should look to Mother Nature for inspiration on how to build human-made, resilient, scalable, evolving systems. There is quite a bit of conceptual capital that exists in this area as well. See Also
Coding From Scratch: A Conversation With Virtual Reality Pioneer Jaron Lanier, Part One The Poetry of Programming Jini Technology JXTA Project
Contact Victoria Livschitz |
| ||||||||||||||||||||||||||||||||||
|
| ||||||||||||