The Way-Too-Early announce: Ecstasy

It's been four years in development, and it'll still be another few years before it's ready to be used in the real world, but we've taken the wraps off the development of the Ecstasy language and pushed a public repo.

The "real" documentation including a full language spec isn't done, but the blog is meant to be a good introduction to the ideas, and relatively easy to chew: https://xtclang.blogspot.com/2016/11/welcome-to-ecstasy-language-first.html

The partial language spec, a text form of the BNF, and other docs are here: https://github.com/xtclang/xvm/tree/master/doc

At a few hundred thousand lines of code so far in the prototype, it's not a small project, but if you're interested in the workings of it: https://github.com/xtclang/xvm

Building this has been some of the most fun that I've ever had as a developer. Finally getting to _use_ it and see it actually running is one of those "sunlit summits".

Peace,

Cameron.

Comment viewing options

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

Congratulations

It looks like a well designed language (at least for an OOP language :D). You seem focused on solving real world problems of the cloud more than novelty, but would you mind musing about what parts of the language might be novel or most impactful vs. existing languages?

what parts of the language

what parts of the language might be novel

It's hard to know for sure what is novel, because it seems that a number of our "original inventions" were also invented elsewhere, perhaps before or after, but it's safe to say that I'm not an expert about what everyone else has previously done :)

- The "turtles" type system (unified object type system, with turtles the whole way down; no intrinsics).

- Reference as an object: Variables are objects, and properties are objects (and properties each define their own class) -- not sure if this is new, but I've never used a language take this approach.

- Funky interfaces - https://xtclang.blogspot.com/2019/07/a-pane-in-glass.html

- The approach to equality (based on funky interfaces) - https://xtclang.blogspot.com/2019/06/the-quest-for-equality.html

- The solution to "null" - https://xtclang.blogspot.com/2019/07/null-is-no-exception.html (although it looks like Gavin took the same approach already in Ceylon)

- "conditional" methods, which are basically a language-recognized formalization of the option type - https://xtclang.blogspot.com/2019/06/conditional-methods.html

- conditional mixins - e.g. https://xtclang.blogspot.com/2019/07/composition.html

- Auto-narrowing types (combined with co-variance) - there's no doc on this one yet. As a feature, it's incredibly obvious and long overdue, but it's hard technically to implement without being designed into the type system. Basically, when a class C refers to "C", it doesn't mean "C", it means "this type", which automatically narrows as the type is used in other compositions.

- Virtual children (which are also auto-narrowing types) ... no doc yet on this one either, but assume something like:

class BaseParent
  {
  mixin MixinChild {...}
  class AbstractChild {...}
  @MixinChild class ConcreteChild extends AbstractChild {...}
  }
class DerivedParent extends BaseParent
  {
  @Override mixin MixinChild {...}
  @Override class AbstractChild {...}
  @Override class ConcreteChild {...}
  }

The call chain for any method on an instance of BaseParent.ConcreteChild is not surprising: BaseParent.MixinChild, BaseParent.ConcreteChild, BaseParent.AbstractChild, Object. What is eye opening is the call chain for any method on an instance of DerivedParent.ConcreteChild: DerivedParent.MixinChild, BaseParent.MixinChild, DerivedParent.ConcreteChild, BaseParent.ConcreteChild, DerivedParent.AbstractChild, BaseParent.AbstractChild, Object.

The ability to "sandwich" behavior is incredibly powerful. The above example is weak, because there's only one ConcreteChild, but what if there were a hundred, like you find in https://github.com/xtclang/xvm/tree/master/src/org/xvm/asm/op or https://github.com/xtclang/xvm/tree/master/src/org/xvm/compiler/ast ? Being able to virtually augment base class functionality in a controlled, non-diamond-creating manner is going to be incredible, but we haven't yet bootstrapped those two toolchains (Compiler and Assembler as represented by the above links to AST and ops) into Ecstasy to understand the full ramifications of such an approach; so far, the most complex use cases have only been white-board simulations and thought experiments. Nonetheless, the combination of a component (the encapsulation of the domain of those children) combined with the ability to compose without inheritance (e.g. aggregation by delegation, e.g. mixins as cross-cutting compositions), and you can much better manage complexity than in a purely inheritance-based hierarchy with an inflexible super-structure.

most impactful vs. existing languages

It's also really hard for me to answer this, because I'm so close to the language. But here goes ...

- Annotations are mixins. This one is terribly useful; see for example https://github.com/xtclang/xvm/blob/master/xsrc/system/annotations/LazyVar.x and https://github.com/xtclang/xvm/blob/master/xsrc/system/annotations/FutureVar.x.

- Proper immutability, with clean "const" construction (i.e. no "this" becomes visible until after the object is immutable).

- Service-based concurrency model, with no shared mutable state (i.e. all memory management is thread local, except for immutable state, auto-proxies, and messages passed). In some ways, I imagine this as being the best of Smalltalk message-passing and the Erlang process model, combined. (I'm not exactly an objective judge, though.)

- Multi-returns - not unique, but this is far too unusual in the PL world for such an obviously useful feature.

- Tuples - again, this is far too unusual in the PL world for such an obviously useful feature.

- Invocation in Ecstasy is very powerful. Both methods and functions are real, first-class citizens. Method invocation support pass-by-tuple, or pass-by either -in-order or -name or combination thereof. Support for default argument values. All of which work with narrowing types using call chain capping (no doc on this yet). Everything above supports currying, both partial and full.

- Proper decimal support (not yet implemented, but the design is for IEEE-754 32, 64, 128, and var-len decimal). This is important because the only thing in the entire universe that uses binary floating point is the man-made computer.

- That said, we will support binary floats (16, 32, 64, 128, and var-len) for hardware-accelerated work (like ML), and n-dimensioned arrays (with 1d & 2d accelerated). For ML, we plan to support compact floats including bfloat16 (not implemented yet), and possibly 8-bit if one gets even semi-standardized like bloat16 is.

- Type parameters aren't just not-erased; they're actually types; this applies to both class-level and method/function-level type parameters. This doesn't seem so big until you go back to using a language without reified types, and by "reified", I don't just mean that the type isn't erased, but rather that the type parameter becomes a type that you can use just like you use the word "String" or "Int".

- All resources are provided by injection; without injection, the language cannot even do I/O. The language has no concept of "that which lies beneath", so access to any system resource has to be injected into an application.

- Using injection, the language is designed for an "unknowable" managed runtime environment (could be the cloud, could be a device), i.e. every bit used, every CPU cycle consumed, every I/O can be tracked (or metered or mocked or intercepted). This is obviously one of the driving factors in the design, and one of the reasons why we could not find a suitable substrate on which to build this. (Originally, I wanted to use V8 or the JVM.)

I suppose the long and the short of this answer is that the syntax is rather minor; we designed the syntax to make it easy for C++ and C# and Java developers to read Ecstasy, and to be able to start writing it almost immediately. The real value is in the type system and the design for the managed run-time, and it will take effort over time to make the value of that design visible.

As an aside, the following example was one of the "aha" moments for me. This index code is basically how I would have written it (i.e. designed the data structure) in C, except here it's completely type-safe and OO -- but without having to build a class for every little detail:

https://github.com/xtclang/xvm/blob/master/xsrc/system/collections/ListMap.x
https://github.com/xtclang/xvm/blob/master/xsrc/system/collections/ListMapIndex.x

There's a lot to unpack in that code, but it's glimpse at what is possible. And that code compiles. And it runs. And it works.

Sorry for the long response ... but it's hard for me to not be excited.

Re: "virtual children"

Is the "virtual children" feature the same as virtual classes as in Beta/gbeta? Or is it a more restricted form of family polymorphism?

Either way, I'd be interested to hear how constructors/initializers (if you have them) interact with virtual children, because that ends up being something with a lot of corner cases in my experience.

Interesting!

Yes, virtual children appear to be a similar idea as Virtual Patterns in the BETA language. Thanks for the pointer!

With respect to constructors, Ecstasy uses a fairly rigid orchestration for the construction of an object, which appears to behave as follows:

1) A structure for the object is allocated
2) The constructor of the class being constructed is invoked with that structure (which is, of course, an object itself)
3) The constructor may invoke other (e.g. super) constructors, or functions, but the methods of the class are inaccessible, because the object itself does not yet exist
4) Once the constructor returns successfully, the structure is analyzed for completeness; to keep it simple, this means that the fields of the structure must have all been initialized by the constructor.
5) Now the object is allocated, and the structure is copied into the object. (In reality, the object is the structure, or the structure becomes the object, but from a programmer's POV, they are two completely different things.)
6) If the object is of a const class ("const", "enum", "module", or "package"), then the object is made immutable at this point.
7) Note that now (and not before this point!) there is a "this". It's a fairly important point in the design that one can never obtain a reference to a const that is not already immutable.
8) If there is a constructor-finalizer (looks like a finally block, but is actually a method related to the constructor), it is invoked with the new "this" reference; this is the first point at which user code sees the "this".
9) After the constructor-finalizer completes successfully, the "this" is returned to the code that instantiated the object.

The orchestration is a bit more complex for mixins and for virtual children, but all of the above still hold true. You should see by logical reduction that it is impossible to make a "const" circular linked list, because no element of the list will have a "this" until it has already been made immutable, thus preventing the closure of the circle. (This is not a problem in reality, because one can programmatically induce immutability; it is simply more work than writing the illegal "const CircLinkListElement(CircLinkListElement next);" as the entire class definition for a circular linked list.)