The design process for Zimbu

Bram Moolenaar, of Vim fame, is designing a new langauge: http://www.zimbu.org/design
What makes this interesting for LtU is that he's making his rationale and his goals public from an early stage.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Allow nominative type a bad idea

Visited the site. Some good ideas. It explains clearly something I suspected, that overriding a definition should be explicit (seeing how important @Override seems in the Java world, and other languages). Adding this to dodo now :)

The idea of giving the choice of using the effective type or the nominative type for objects, via explicit mention of the type interface, is misleaded in my opinion. When writing the code of a class I don't want superclass methods to be used on its instances at will. The methods I redefine should always be used. Ultimately the class designer should decide on effective or nominative typing.

I am not convinced that the possibility to apply a mixin several times is worth the added complexity. In dodo it is a no-operation.

Good luck to Bram with Zimbu :)

re: @Override

i think, once again, C# gets it waaay more right than Java.

But what is it?

I couldn't get any very clear idea of what the language is, as opposed to “All the good parts of other languages, with none of the bad parts”. There seem to be some very strange ideas: according to the ‘Goals’ page, Python and Java are not used, because they are slower than C++. That may be true in an individual project, but I think that it ignores the vast code base of Java, and the respectable code base of Python.

C++ is described as “the most feature-rich language” in his list of ‘Inspirations’. It may be the most feature-rich on the list—I don't know enough D and Boo to speculate—but I think it's far from the most feature-rich available language available today. Do we really need another C-ish?

Most of his ‘Inspirations’ seem to be concerned with syntax to adopt or avoid. While I know that syntax plays a huge role in the usability, and even expressiveness, of a language, do we really want a language that's developed around mostly syntax, rather than around mostly ideas?

(Maybe this is too harsh, but I'm a huge fan of vim, so I was really excited to see a breath-taking new idea in programming.)

Based on 'Inspirations'

I would say this aims to be a portable applications language, akin to Java, modified by a few additional features (in particular: 'auto' types, primitive support for common collection idioms), plus a wishful assertion of 'performance'.

It is reasonable that you are skeptical of this. It isn't a breath-taking new idea; it is, instead, the beginning steps on a path that almost every new language designer walks at some point.

In this particular case, I suspect the author will eventually discover Scala and similar pursuits (even Digital Mars D, which he mentions) that are much further along on this same path he is walking, and that his own preferences for syntax alone are unlikely to be worth the cost of investing in a new language.

I'm personally disinterested in new applications languages (by which I refer to code that explicitly hooks and controls a UI shell, be it a console or Windows or HTML+DOM) unless it offers something new in terms of policy injection, configuration, dependency injection, concurrency, distribution, persistence and disruption tolerance, graceful degradation, security, or safety proofs over hardware interactions other than CPU+Memory. I'd much rather search for a path to ending entirely the 'applications' approach to user interfaces .

I'd much rather search for a

I'd much rather search for a path to ending entirely the 'applications' approach to user interfaces .

Would you say Backus's Turing award lecture was the siren call? ...Just trying to nail down what 'applications' approach means. To me, it entails all of the physical coupling issues inherent in all 3rd generation languages.

"Applications"

I haven't read or heard Backus's Turing-award lecture.

But the 'applications' approach to to UI involves two systems: Shell and Controller. The Controller-code explicitly manipulates the Shell, and performs other IO as necessary in the background. The Shell routes some inputs back to the Controller, representing user-input. The Shell may support multiple controllers simultaneously, and thus routing certain user-inputs is part of its job. A 'shell' might be graphical or console.

The weaknesses of this 'applications approach to UI' are so many and varied that it is difficult to make a list, but here's a stab:

  • input routing confusion: a controller may 'work around' the shell to obtain sources of user-input, such as joysticks and web-cameras. These inputs will not be appropriately routed.
  • composition: there are at least three forms of UI composition that are useful: transformation (like cascading style sheets), inheritance with partial overrides or additions (mashups, annotations), and linking/navigation (including transclusion). Application UIs rarely support any of them.
  • coupling and portability: the controller is coupled to the shell at time of design. This coupling results in a more complex UI design - applications are built 'for' a particular platform and shell, and thus are less portable.
  • scalability: the client, server, and network costs of applications approach to UI are proportional to the number of applications 'open' rather than demand. Ideally, processing costs for UI elements should scale linearly with active demand. This allows millions of UIs to be logically 'open' at the same time and allows 'zoomable' UI composition. (Related: Microsoft DeepZoom (previously Seadragon) [wikipedia])
  • mobility: without special effort, one may not 'move' an application from one host to another. This is hindered by the behavior of the controller being coupled to the shell, often in a retained-mode manner. The workarounds to other input sources don't help either.
  • persistence: without special effort, applications do not automatically 'persist' between access. This relates to scaling issues, too, since full scalability requires UIs that may persist inherently.
  • sharing: there is no inherent principle supporting concurrent multi-user access to a UI provided through a shell. Attempts must work-around the shell itself, such as sharing a whole desktop, but this will be even worse with regards to 'routing confusion' described above. Sharing is useful for 'embedding' of input elements (e.g. if I update a text area, and the 'same' form is embedded in your page, ideally you can see my update live and in real-time). Croquet Project is largely about sharing.
  • programmability: application designs for UI are typically not extensible or modifiable. This relates to internal extension, such as adding a menu item or adding a new input element, as opposed to external composition via inheritance. Grease Monkey, Firefox Extensions, and Chrome Extensions are starting to re-introduce programmable UIs again. Read-eval-print loops and such have always provided this feature.
  • security principles: application designs for UI can (and typically do) hide legitimate capabilities and expose capabilities that cannot actually be fulfilled. Ideally, users should be aware of what they can do to whatever level of precision they desire. (Related: Capability User Interface [c2]). This exposition is hugely important for both composition via inheritance or transformation, and programmability.

Alternatives to the 'applications' design include RESTful styles, and publish-subscribe. The latter two can be combined quite effectively via Functional-Reactive UIs, and tend to scale well and be demand-driven. There is also the Croquet Project duplicated computations approach, though I don't favor it.

My own pursuit on this vector is mostly exploratory at the moment, but you could read document definitions [c2] and declarative gui [c2]. The former clarifies 'application model for UI'.

I'll note that web-applications ARE applications. Java Script + AJAX is a controller for a Document Object Model 'shell'. However, web apps (minus plugins) do avoid a few issue (e.g. no joystick routing, thus no confusion...).

I'm uncertain what you mean by "physical coupling issues".

Interesting

I wrote some basic blahg posts toward discussing such limitations when I was assessing why I don't follow Microsoft Patterns&Practices division's guidance for GUI Architecture. There are other eventual weaknesses of any such Shell/Controller architecture that are symptomatic of any Controller-based architecture, such as Presenation-Abstraction-Control and the Frame-Oriented Programming architectural style. Most notably, people do things like create SubControllers - I've seen this idea in so many different forms and just intuitively know it is messed up.

I also commented (argued?) to a MS architect that GUIs are fundamentally a dynamically distributed, dynamically federated system and that the real trick to modular end-user composable interface will be some clever and simple design strategy that takes into account those factors.

scalability: the client, server, and network costs of applications approach to UI are proportional to the number of applications 'open' rather than demand. Ideally, processing costs for UI elements should scale linearly with active demand. This allows millions of UIs to be logically 'open' at the same time and allows 'zoomable' UI composition. (Related: Microsoft DeepZoom (previously Seadragon) [wikipedia])

Likewise, if you break an application into multiple controllers, then you have multilpe applications, then the only way to coalesce the event queue is to have leaf nodes talk to each other. In a Shell/Controller paradigm, this either requires a global variable or hardwiring sequences of collaborations, turning a class hierarchy into a controller tree (where superclasses have root control of leaf classes by "choosing" which one to use) instead of a problem domain model. Thus, controllers can very easily make duplicate and triplicate, and so on requests for the same data, which is exactly what you are saying about "open applications".

John Ousterhout's Fiz framework commits this sort of Original Sin, because, as you might note, his focus is what his course syllabi say: "The Web was originally designed as a mechanism for delivering documents, not applications." I tried telling a Stanford grad student that this basic problem statement puts you back 30 years, and that this is not the way to do a thesis. Instead, you start with a question, not a problem. There is a reason Fiz is not a problem worth solving, and there is a reason why the Web was not created to solve it. Modern blog writers might suggest otherwise, but they're wristy gestures and handy poses.

programmability: application designs for UI are typically not extensible or modifiable. This relates to internal extension, such as adding a menu item or adding a new input element, as opposed to external composition via inheritance. Grease Monkey, Firefox Extensions, and Chrome Extensions are starting to re-introduce programmable UIs again. Read-eval-print loops and such have always provided this feature.

Could you clarify what you mean with regard to external composition via inheritance? In particular, why inheritance?

The reason why I ask is that traditionally 3D and CAD modeling software packages typically have gone the route of "external references" or "XRefs". Effectively, an Xref allows a scene designer to include an external object in the scene by reference to a resource with a media type that the environment can import. This isn't really inheritance so much as a dependee being injected into a project. As an imaginable alternative, you can in theory stamp-and-clone a resource, and LINK the clone to the original.

Also, by not using inheritance, I can decouple the declarative aspects of a W3C resource (naming and identification) from its functional aspects (addressing and media type handling).

persistence: without special effort, applications do not automatically 'persist' between access. This relates to scaling issues, too, since full scalability requires UIs that may persist inherently.

While not a baked in flaw, they also usually lack fault tolerance / disruption tolerance when automatic persistence is not provided. Nobody seems to understand this, though. Especially Microsoft, whose council of wise men are claiming "Entity Frameowrk is "much bigger than an Object/Relational Mapper" (it has no mechanism for recovery, and includes some features that encourage the distributed shared memory model that failed in research labs in the '80s).

security principles: application designs for UI can (and typically does) hide legitimate capabilities and expose capabilities that cannot actually be fulfilled.

This is not easy to get right, and if you write your code using traditional C-style data structures + algorithms, you have no shot. Likewise if you use encapsulation and information hiding, because none of these solve the problem of actually modeling the problem domain and striving for problem domain stability. Daniel Jackson has a good example of this he always shows as his prime motivation for the Alloy model checker: Apple's mail software has a poor model of users, groups, and aliases.

Not 'OOP' inheritance

I mentioned 'inheritance' under 'composition'. It is, very simply, any form of composition that allows you to take one UI and add, remove, override UI elements.

Multiple inheritance may also be useful.

One may inherit by some sort of 'external reference' to a 'document object' (extrinsic identifier for a UI description). But keep in mind that several other forms of composition also involve 'external reference', such as linking and transclusions, that do not suggest the ability to manipulate the UI elements available to the user.

Further, there are reasonably other forms of inheritance that do not involve reference to any particular object. In a very flexible system, one might start with an open functional-reactive query across two databases, then transform it further to develop the UI, as a sort of functional-reactive approach to UI inheritance.

If you must relate it to OOP, think 'prototype-based OO systems' among others. Inheritance is essentially a word for implicit delegation with explicit override.

Disruption tolerance, Security principles

I agree with your comments on disruption tolerance.

I'd further note that RAII and IDisposable patterns are largely pointless in a persistent language, as resource access would be a feature that must automatically regenerate whenever the application hops out of hibernation, and must automatically release when going into hibernation, and inherently ought be able to do so selectively and lazily based on anticipated and actual demand during activity. Combine persistent language with software transactions (or some other alternative to locks) and there should never be a need for explicit cleanup. These are useful properties for an anti-application language.

For security principles, I have some ideas on a 'right' approach.

I believe such an approach will involve representing a 'UI' as a 'set of capabilities' combined with information resources and 'suggested presentation elements'. The UI would need to be browser-oriented such that users may readily browse capabilities.

In such a description, there would be no scripts. In place of scripts, one would have external 'object references' (which serves as an object capability). By avoiding scripts, there is no possibility of a capability being embedded in a script, thus no 'internal' information hiding or encapsulation. Further, the semantics of every object-reference in the UI description would be implicitly specified based on the events that object receives. Therefore, the set of capabilities available through such a UI is fully specified. This is quite useful for composition (inheritance, partial overrides, selective UI subsetting such as wiring up a button from form X into UI Z).

By comparison, if object capabilities could be embedded in scripts, then it would be possible for a browser or user to obtain a reference to an unspecified capability that was pulled from the script, which would violate security principles.

One advantage avoiding scripting 'logically' is clarifying the limits of UI 'capabilities' - the inability to peek inside 'scripts'. Other advantages come of supporting 'objects' independently from the UI: references to the same object will maintain across hosts, across browsers, across users, and over the course long sessions, thus reducing downloads, supporting persistence, allowing mobility and multi-user sharing - a feature-set near impossible to achieve with 'cookies'.

Trivially, of course, one can transform an interactive scene-graph with event-scripts into one without by creating an 'external' object per event-script. This trivial approach is likely to be unnecessarily expensive.

The event objects should not take their UI-context as a parameter. I.e. you rather than sharing one object among ten buttons and passing in click("Button3"), you simply have one object per button and pass in 'click'. This is much better for security principles (especially selective UI subsets), and far simpler for composition (such as adding just Button3 to some other UI component).

------

To the above list of flaws, I should also add accessibility (application approach to UI doesn't readily allow transformations to support blind, deaf, multi-language, etc.), and transaction or undo (no implicit support for performing multiple operations then 'committing' or reversing. Certainly no support for issuing multiple operations across independent applications, then 'committing'). Any UI approach ought to aim for these features, too. Transactions are actually easier to achieve than accessibility...

I'd further note that RAII

I'd further note that RAII and IDisposable patterns are largely pointless in a persistent language

Since you support capabilities, what about revocation? The ability to revoke access to a resource is similar to disposal, and one may wish to dispose of it immediately if one knows that revoking it leaves only the one reference.

I also disagree that persistence, orthogonal or otherwise, means resources should be automatically reconnected on exit from hibernation. The resource may no longer exist, or access may have been revoked, or the network may be inaccessible, or... One can make a case that failure and disruptions are similar enough to persistent restart, that they could be handled by a sufficiently general mechanism. E and EROS have run into these issues with orthogonal persistence, and some manual persistence seems inevitable.

Revocation

Since you support capabilities, what about revocation?

Sure, revocation is still useful. However, revocation is not the same as disposal of a resource; it simply means cutting access from a resource, which requires an indirection where the cut can be made.

I also disagree that persistence, orthogonal or otherwise, means resources should be automatically reconnected on exit from hibernation.

Nonetheless, what persistence means is automatic reconnection to communications resources. Memory and state is just one example of a communications resource.

The resource may no longer exist, or access may have been revoked, or the network may be inaccessible, or ...

If a resource has been revoked, that's fine: any revocable resource must be revocable between any two given operations, so revocation during a period of hibernation is perfectly legit.

If a resource no longer exists, that's also fine, though the resource would again need to be one abstracted to its user as having an asynchronous existence (pretty much the same as revocation).

If a network is ever accessible, then the language (or framework) can usefully support a model of distributed resources that may be temporarily 'disrupted' at any time (orthogonal to local persistence). Desirable properties include graceful degradation and resilience (rapid recovery). The persistence mechanisms offer a useful approach to regeneration, but the language must still design in some semantics for behavior when a relevant part of the network is partitioned.

It helps to have good support for replicated objects, data-binding and effective caching (good enough slow-updating values), fallbacks defined into data and event source expressions, ability to decide between 'reliable' and 'unreliable' message sends, etc. It also helps to avoid some unsafe features, such as locks, synchronous waits, etc. Avoiding mutable state where feasible helps a lot.

One can make a case that failure and disruptions are similar enough to persistent restart, that they could be handled by a sufficiently general mechanism. E and EROS have run into these issues with orthogonal persistence, and some manual persistence seems inevitable.

There is no requirement for manual persistence... or, more accurately, "manual persistence" in a persistent language consists of manually storing a value to any cell, then later pulling it out of that cell and working with it. All cells are persistent, therefore all such operations constitute manual persistence. That sort of behavior may be useful for certain patterns.

E and EROS did not expend much design effort on disruption semantics, or on approaches to live-programming. Detection of disruption is supported through a 'miranda method', which was sort of tacked on later and requires a good bit of micro-management for anything but the simplest of patterns.

Explicit destruction of objects, especially cascading destruction of objects can be useful to the problems described in the EROS page. One might have a 'dependency graph' that is independent of the 'reference graph', then introduce a 'killer-cell' primitive that dies on command (capability-secure destruction) and takes out all objects that 'depend' on it. This can serve in four primary roles: (a) revocation of capabilities (via destruction, which is better for GC), (b) escalation of a subtle partial-failure into an obvious catastrophic failure (which may be easier to handle due to avoidance of micro-management), (c) process control and accounting (ability to destroy, and optionally replace, a configuration of objects), (d) use 'dependency graph' along with known distribution to decide how to properly distribute redundant backups of objects in the case of node failure (i.e. if A depends on B, and B's node is lost, A could know how to regenerate B and might have been keeping a mirror of the master B).

I'd further note that RAII

I'd further note that RAII and IDisposable patterns are largely pointless in a persistent language, as resource access would be a feature that must automatically regenerate whenever the application hops out of hibernation, and must automatically release when going into hibernation, and inherently ought be able to do so selectively and lazily based on anticipated and actual demand during activity.

What on earth does largely pointless mean? This isn't to say I've never been caught using such a phrase, of course. Just taking my hammer to your words and nailing stuff down.

Largely pointless =

By calling RAII and IDisposable "largely pointless" I mean "every use case that I have encountered, and every use case I have thus far imagined, is deprecated; however, to be fair, there may be a use case that I have neither encountered nor imagined that may still justify use, so I'd rather not commit to a 'totally pointless'"

I apologize for the confusion caused by my fear of commitment. ^_^

Vala

I think the main constraint ruling out languages like D is the desire to deploy easily to a wide range of existing operating systems and hardware (hence targeting C). This makes sense for the maintainer of vim, which has active OS/2 and Amiga ports. Zimbu's motivation seems very similar to Vala which is already being used in a number of GNOME desktop applications.

(I agree that Zimbu also seems to place a rather high priority on syntax features.)