First, some general remarks about E's goals, and how they probably differ from Mozart's.
E was explicitly designed to seem immediately familiar to people coming from the C syntactic tradition (C, C++, Java, Python, csh). As we introduced functionality novel to those in that tradition, we took pains to package it in ways that seem familiar. This was inspired by Bjarne Stroustroup's success at shoehorning objects into the C tradition, sort of, and thereby successfully teaching objects badly to the world. (In "The Design and Evolution of C++", Bjarne explains that, as a computer scientist, he doesn't like C++. C++ was language design as marketing strategy.) Java's success is a similar story, made possible only by the education given the world by C++. Both languages succeeded despite the prior availability of far better languages.
Sorry for the trip through the sewer -- we hope to avoid stooping this low. However, we are trying to educate the world in a old paradigm -- capability computation. Despite beautiful previous vehicles, this paradigm never made it, and has become virtually unknown in the present world. At the same time, the present world has become wildly familiar with object programming, and capabilities are only a small distance away. In just the way Stroustroup and Gosling each successfully engineered-for-familiarity in order to teach the world their lessons, we are trying to teach the world capabilities, and to get distributed cryptographic capabilities into widespread use. Unfortunately, the objects the world is familiar with are C-tradition objects. But I'd rather design a successful somewhat ugly vehicle, than repeat the failures of the previous beautiful vehicles.
I've been looking at Oz. I'm very impressed. It's beautiful. In many ways, it's more beautiful than E. I don't know what your goals are. For our goals, in my judgement, Oz is simply too unfamiliar to the dominant paradigm. It's not just a matter of syntax. Even if you perverted Oz to have a C-tradition syntax, I believe its semantics will seem too peculiar. E's immediate ancestor, Joule http://www.agorics.com/joule.html , is a minimalist language as clean and beautiful as Oz, but it was too hard to teach. I've seen *much* more success teaching E, even though I know it's much more complicated.
Just as C++ made it possible for the world to understand and use Java, I would be honored if E's only long term effect was to make it possible for languages such as Oz and Joule to succeed and spread the distributed capability paradigm. If I thought it were possible for them to succeed now, I wouldn't bother. Joule already embodies the principles of computation I care about, and I believe Oz can be made to, and in both cases the result would be more beautiful than E.
At 12:35 AM 1/26/00 , Peter Van Roy wrote:
>About the "constraint-based inference", here's an unabashed attempt to
>persuade you :-). By "constraint-based inference" we actually mean
>something quite more general, "any kind of complex symbolic calculation".
>This can range from simple stuff, like manipulating lists, to real
>complex stuff, like efficient natural language parsing. Manipulating lists
>is much more cumbersome (and 10x slower) in Java than in Oz. Lists are a
>simple case; symbolic calculations in Oz scale up to quite sophisticated OR
>problems.
Well, I'm all in favor of "any kind of complex symbolic calculation". Ignoring the slow implementation and lack of environment, but speaking only of the language design per se, I'd say E is already as decent a language for symbolic computation as Scheme or Smalltalk. The ways in which it is better than these (for symbolic computation) are due to its logic programming ancestry, and are therefore also the ways it approaches Mozart. I suspect these are the issues you have in mind.
In keeping with the packaging-for-familiarity, E's logic-programming-derived features are covert and need to be pointed out. There are two main such features: pattern matching & Promises. In addition, for constraint programming, E's Slot abstraction enabled me, in E, without extending the language, to create a weak constraint system more inspired by Borning and Sussman & Steele constraints than Vijay constraints. (I consider Vijay's work much deeper and clearly brilliant, but the other kind of constraints fit into my framework much more easily.) I will cover pattern matching in this message, and Promises & constraints in later messages.
Pattern Matching
Unification is too powerful. Instead, E has pattern-match-and-bind. There are five contexts in E's grammar where a pattern can appear:
define foo(x, [y, z]) { ...
defines a function named "foo" that, when called, will define and bind "x" to the first argument, will require that the second argument act like a list of two elements, and define and bind "y" and "z" to each in turn.
In expressions, "+" can be used to append two lists. In patterns, it can be used to match against initial elements + the rest. Here's the old Prolog append in E:
define append(Xs, Ys) :any {
switch (Xs) {
match [X] + RestXs {
[X] + append(RestXs, Ys)
}
match [] {
Ys
}
}
}
2) Above we see another context where a pattern can appear: in the head of a match clause. Notice that the "[X] + RestXs" appears in a pattern context, so both variable names are defining-occurrences of those variable. Whereas "[X] + append(RestXs, Ys)" appears in an expression context, so the variable names are use-occurrences.
The above is syntactic sugar for:
define append(Xs, Ys) :any {
define t1 := Xs {
if (t1 =~ [X] + RestXs) {
[X] + append(RestXs, Ys)
} else if (t1 =~ []) {
Ys
}
}
}
Here we see two more contexts for patterns: the left side of a define and the right side of a "=~".
3) In E, the scope of an identifier lasts left to right starting from it's defining occurrence until the scope ends with a "}". Inspired by Colmerauer's Prolog III, we applied this consistently to the define construct, thereby enabling the easy expression of cyclic data http://www.erights.org/elang/blocks/defVar.html#scopeConsistency .
I see that Oz also took a principled Prolog-III-like stand on cyclic data. Very good.
4) The "=~" operator is borrowed from Perl, but the patterns that can appear on the right are the same as the patterns that can appear elsewhere in the language. Because of the left-to-right rule, "=~" cannot be used to define cyclic data. More interestingly, whereas define throws an exception if it fails to match, "=~" returns a boolean indicating whether the match succeeded.
5) Finally, as the iteration variables of E's for loop http://www.erights.org/elang/blocks/forExpr.html . In the expansion of this syntactic sugar, these patterns end up on the right side of a "=~".
Making logic programming analogies, #1 is like the head of a 1-clause predicate, whereas the switch statement of #2 is like a multi-clause predicate. Both #3 and #4 are like explicit uses of "=" in the body to cause unification. "define" is like "=" after the commit point in a language with committed choice, whereas "=~" is more like "=" in the guard. #5 doesn't directly correspond to anything in logic programming, but it's trivial sugar for a pattern of the other constructs, so there's nothing fundamental going on here.
Besides defining occurrences of variables, square-bracketed lists of sub-patterns, and "+", E has a few other primitives for defining patterns http://www.erights.org/elang/grammar/patterns.html . However, primitives provided by the kernel can only take apart data types understood by the kernel. (In fact, lists are the only data type taken apart by kernel-provided patterns.) More interesting is that these primitives are adequate to enable the E programmer to effectively extend the pattern language to deal with any data type they'd like http://www.erights.org/elang/grammar/quasi-overview.html . I know of no other symbolic computation language that provide this latter ability with anything like this power.
>One of the things that Oz supports efficiently is 'compound values'. Think
>about it: integers are values, characters are values, but it's very rare to
>have *values* that are bigger than than one memory word (lists, trees, ...).
>Certainly not in C++ or Java. In those languages, values are "patched
>together" with pointers. A compound value, on the other hand, does not
>change. It can be sent across the network, and the language semantics
>guarantees it will not change.
Peter, I confess I have not been able to make sense of this paragraph. Certainly the semantics of C++ must talk about pointers, and the C++ programmer must be painfully aware of them. But I don't see how they show up in Java's semantics any more than they show up in Oz's. If the issue is only immutability, it's easy in Java to write a class has only immutable instances: make all the instance variables "final".
As far as being sent across the network, E has an explicit PassByCopy
declaration (misnamed "selfless" in
http://www.erights.org/elang/same-ref.html ). Like Oz values, an E
PassByCopy object is immutable, is transparent (reveals its state to its
clients), and has value-based equality rather than creation-based
equality. Unlike Oz, their behavior is fully programmer definable in the
normal object-oriented way, subject only to the immutability and
transparency constraints. Our PassByCopy data types are fully as extensible
as our other data types. Have you considered allowing the non-privileged
creation of new value types?