> "I'm really curious to hear what you saw was missing in Kotlin for serverless cloud that made you go "We need a new language"."

I should start by explaining that when we say "serverless", we're not talking about "lambdas" or something like that. When we talk about serverless apps, we're talking about full-blown, scale-out, stateful applications with their own database(s) and so on.

To answer your question, Ecstasy is _not_ really a new language, just like coming from C++, Java wasn't really a new language -- it was just a significant (and positive) revision that developers could easily grok. Similarly, coming from Java or C#, Ecstasy should be viewed as a language revision. But why revise at all?

That's a great question.

There are two significant reasons that drove us in this direction. First, that the runtime model (Java, C#, Kotlin, etc.) was the wrong runtime for what we were setting out to do. It has virtually unlimited surface area (hey! I found a JAR file at some special place on the file system, so yeah, its API is automatically available to you if you ask for it!), no closure over its type system, and unlimited access to its environment -- the OS, the file system, the network, and so on. This may seem minor, but for our goals, an application had to be truly sandboxable, and its environment 100% injectable.

(To wit: Security cannot be _added_ to a system. A system is either entirely secure by design, or it is insecure. Security is not a "manager".)

Gene saw your question and wanted me to explain how that surface area (anything outside of the application) is unseeable, unknowable, and undiscoverable by design -- even when using Ecstasy's quite powerful reflection capabilities. If you look at testMaskReveal() on the reflect.x test -- https://github.com/xtclang/xvm/blob/master/xsrc/tests/manual... -- it might give a glimpse into the thinking in the design, which is that an object _reference_ can injected into a container such that its type is _masked_.

Conceptually, a reference in Ecstasy is a combination of a pointer to an object (i.e. the object's identity), and a separate pointer to its type. This is fundamentally different from C++/Java/C#, in which a reference is a pointer to an object, which in turn begins its structure with a pointer to its own type (e.g. a "v-table"). You can see some of this discussed in the documentation on the maskAs<Masked>() method of Ref (i.e. an interface for any reference), found here: https://github.com/xtclang/xvm/blob/master/xsrc/system/Ref.x

To make a long story short, injected resources are automatically masked to their injection (interface) type, and even using reflection, nothing beyond that mask can be made visible. The only ability to interact with the outside world is injected, and those injected references are limited to the surface area of the injection type, which is dictated by the injector. Nothing in the type system points to (or can point to) anything outside of the type system; there are no "intrinsic" or "primitive" types, and no secret native things hidden inside an implementation (trying to hide inside some private field or method).

(At this point, I'll stop going down the path of this specific discussion, just to make sure that I haven't made the topic even more confusing. Follow-on questions are welcome.)

We also spec'd that an application also had to be truly manageable. An application runtime that allows the application to "run out of memory" or "create too many threads" or "burn too much CPU" is not manageable.

Secondly, most of the runtimes that we evaluated were either single threaded from a language execution POV (e.g. V8/JavaScript/node), or they were global-mutable memory models -- typically with the threading managed at the application level (a la "new Thread(...)"). Neither of these approaches was acceptable, because we had a specific technical goal in mind, which was to be able to make efficient use of hundreds-to-thousands of concurrent hardware threads (e.g. cores) utilizing tens-to-hundreds of terabytes of RAM, in a managed memory environment with automatic GC, all without any stop-the-world GC pauses.

To accomplish this, we designed the runtime as a hierarchically subdivided (n-ary tree structure) software container model, each associated with a type system (a fixed set of modules) with transitive closure. Within a container are any number of services (including the container itself, which is a service), each of which represents a Turing machine with its own dedicated memory. Services are conceptually asynchronous vis-a-vis other services, and can only exchange (through "the service boundary") immutable data and service references. Calls through a service boundary are potentially async; one can use a "future" (aka a "promise") to manage that, or (by default) the async/await mode is automatic. The result is akin to a message-based or actor-based model for achieving threading -- but yet without even having a “thread” object.

One result of this design is that GC is done at the service level, not at the runtime level. (A service is equivalent to an Erlang process, if I understand the Erlang architecture correctly.) Service concurrency (threading and synchronization) can be managed by the runtime; it can be profile-driven, at runtime, much the same way that Java's Hotspot compiler can choose what methods to inline. Native code can be generated as if there is no concurrency, because all mutable state is bounded within each service, so no two threads will ever be writing to the same memory area.

(If you have worked on assembly level optimizations, cache optimizations are the biggest possible low-level optimizations for most programs today. Going to main memory today is about as _relatively_ slow as doing a dynamic memory allocation was back in the 90s -- and it was considered verboten! Avoiding cache flushes and critical sections and CAS operations that are side effects of memory model concerns is a _huge_ win!)

Lastly, the recursive software container model has some significant benefits, such as (i) being able to safely load and execute _untrusted_ code, (ii) being able to safely "delete" an entire software container at runtime (or even an entire sub-tree software containers), with almost no cost of resource reclamation, (iii) being able to reload an entire software container and transition it into a running state, almost instantly.