Hi Matt,
more inline
On 9/2/26 17:14, Matt M wrote:
Hello,
I read through the proposal and it sounds interesting. My initial
impression is that it might not have a lot relevance to end users (maybe
it does for specific library writers or as a kind of "constraint" around
certain code? (I hesitate to say anything else or in other ways here in
so far as to draw incorrect or unintended correlations)). In its current
iteration, it seems focused on being a compiler level enhancement/
requirement for future refactoring and redesigns than a necessarily
highly applicable, broadly relevant user facing feature. None of which
is bad.
yeah, I guess I was emphasizing the compiler part too much.
Some problems this tries to solve:
**Extension methods**
* In the static compiler, extension methods from a library dependency
can influence your code, even if you use nothing from that library.
* Extension methods are not available for internal use only. A library
cannot provide extension methods for its own implementation without also
making them available to users.
* Extension methods may have to be written in Java to avoid interference
from the MetaClass system.
* Users cannot define their own extension methods in the same way as
library-provided extension methods.
**Categories**
Categories are the user-level variant of extension methods, but their
scope is problematic because it is thread-bound.
* Most categories are actually intended to apply to a call site, which
is why this GEP discusses lexical categories.
* Dynamic categories complicate the implementation and affect every call
site that Groovy resolves dynamically. Being able to turn them off
permits a simpler and more reliable dispatch implementation, which also
benefits tooling and other implementations of the call site.
**Groovy core and Groovy modules**
* Some parts of Groovy core, such as extension methods and the MetaClass
implementation, cannot be written in Groovy itself without depending on
the dynamic mechanisms they implement. This is not a proposal to rewrite
Groovy in Groovy tomorrow, but currently there are places where this is
not possible at all.
* The old MetaClass system has accumulated several generations of
implementation techniques: reflection, generated call sites, primitive
optimizations, invokedynamic, and others. It needs a redesign that is
largely blocked by the requirement to preserve the legacy API.
**Optimized dynamic compilation**
This is already discussed extensively in the GEP. The basic idea is
dynamic Groovy with compile-time optimizations controlled by which types
and dynamic features are available.
For the user in short, more control, more tooling support under some
specific conditions, a new construct for categories.
I believe I got the gist of the idea that the GEP is getting at. It
would probably be really useful to call out and accentuate the exact
purpose and mission statement of the problem(s) that it seeks to solve
(e.g. "Start to work towards enabling strong encapsulation and isolation
of dynamic capabilities in a controlled and deterministic way in so far
as to prevent unintended leaks of the MOP and MOP-like functionality
outside of an articulated scope.”).
the mission statement I would probably phrase along this:
The goal of realms is to make Groovy's dynamic semantics explicit and
controllable. A realm defines which dynamic capabilities are visible to
a piece of code, allowing extension methods, categories, the MetaClass
system, and other dynamic behavior to be selected, isolated, and
optimized independently of the surrounding application.
"which dynamic capabilities are visible to a piece of code" does not
mean encapsulation in the Java sense. This is not an OOP concept.
Optimization is a byproduct of having more control - control a human and
the compiler can understand.
Can a user continue writing his programs as before, most likely. The
intention is not to break existing code with this. I have to see if that
is actually realizable, but I think it should. And if there are changes,
then in the meta class api maybe.
At a linguistics level for the idea, I think the term |Realm| is not
quite a good fit for describing the concept. It's quite whimsical and
stylized without quickly and clearly explaining/alluding to what pattern
of functionality it represents or does, even colloquially. I would
suggest changing it to some version or combination of the various
explanatory terms/word-phrases already used in the GEP to explain/
describe the idea itself such as |Environment|, |Scope|, |
EnvironmentScope|, |ImmutableContext|, etc. just to name a few. For
example, an initial idea of a clearer design that came to mind was that
a |@Realm| (i.e. in usage form) is better described as a |@Scope| and a
|@RealmDefinition| is described as an |@Environment|. Therefore, one way
a user could think of it is as "You define an @Environment and then use
that environment in/as a @Scope for code to execute in/under". Makes the
mental model clearer and more explicit.
Names can still be changed, the concept is what needs acceptance first.
To give you an example of my impressions... When I hear scope, I think
block scope, variable scope and so on. Environment I think of a system,
environment scope I have no idea. If I had to do more with Kubernetes I
would probably associate realm with that world. Just see the names as
"work in progress".
On a minor hiccup I had in my readings is the word "properties” being
used. I initially got a little confused at how it was saying that these
concepts would interact with and work with Properties (i.e. Groovy's |
getters/setters|) since it said: "A realm inherits the definition of one
other realm and may replace individual properties.”. I incorrectly
conflated the term with the Groovy literal idea of |Properties| which is
not what was intended. It might be better and clearer to say that what
the GEP is referring to are environmental context attributes or
something else of that kind.
For now I will then just use attributes, which really only works better
against Groovy and Java developers, because attributes is not used so
much there to describe fields.
At a semantic level, overall, the GEP is going a bit deeper than the
idea that might be canonically known as namespaces. It's closer to
implementing specific resolution handling and isolation rules/semantics
for strong encapsulation of/for the MOP. I tried to draw a corollary to
other languages that implement the same concept or at least notionally
seem to go for the same idea. The best one that that I could arrive at
was idea of "function environments" in Lua. GEP-31 seems similar in
vision albeit highly constrained on the MOP and extension methods
exclusively in the current iteration.
Dart, Kotlin and Scala provide statically scoped extension mechanisms,
Ruby refinements are closer to lexical categories. JavaScript realms
instead isolate global execution environments. These mechanisms address
parts of the problem that realms address, but none combines them with
explicit control over Groovy's MetaClass-based dispatch.
Frankly the whole problem is home made and rooted in the complex MOP
[...]
* How does backwards compatibility work in so far as the compiler’s
public API/ABI is concerned? At first glance, it seems completely
transparent and seamless. However, I wouldn't want it to be what
project jigsaw was to Java (i.e. probably the least understood, most
hated/disliked feature ever implemented in Java; that was largely
seen as solely for the benefit of the compiler/language devs at the
expense of everyone else). [I know that I am probably editorializing
a fair bit here but based on my experience and interactions with all
kinds of other developers in the Java space and even what the JVM
architects said themselves, it's not really a disingenuous or even
an unfair perspective].
well... good that you did not see some of the mails I exchanged with
those guys back then. That is all I will say about me not liking jigsaw,
or I will rant for a while.
I don´t think the ABI will change for this. I do not see where and what
for. There will be a new bootstrap method, to take the realm as
parameter, but the old one will continue to work and assume GROOVY_DYN
realm, which is 100% compatible to today.
* What are rules for inheritance as it relates to a Realm and all of
its layers' priority and ordering? How does a Realm inherit from
another Realm and how do we envision that working (not from a syntax
perspective but rather like how do we envision that actually working
under the hood)? What is the mental model for its inheritance that
is made clear for end users (folks in general struggle to understand
OOP inheritance as it is already)? There's a lot of talk about
layers and there seem to be quite many and they have various planned
intricacies for how they each interact with each other and those
above/below each other, in regards to precedence and priority.
Realms do not strictly inherit in the OO sense. A realm definition
declares capabilities. Extending another realm means taking those
capabilities and then overwriting selected ones with your own
definition. The resulting realm is an ordered composition of layers.
The usage side determines where the definition is applied. The usages in
the proposal are use(MyRealm) { ... } and @Realm(MyRealm). MyRealm is
more or less a token given to the runtime.
At runtime, that token is associated with an ordered list of layers per
class. The exact shape is not defined yet and will depend on the
implementation, but most likely there will be one layer per extension
method class, one for the small MOP, one for the extended MOP, and one
for mutable MetaClasses.
To dispatch a method, the runtime goes through the layers in order. Each
layer determines which targets are applicable; the applicable targets
may be empty. The most specific applicable method is selected as the
dispatch target. If no layer provides an applicable target, the dispatch
results in MissingMethodException.
Think of each layer as a specialist. We already have most of this
machinery in current Groovy, both as code and partially as a model - we
call it a meta class. The proposal separates the code paths and models
the dispatch environment more explicitly. Once it is represented as a
model, we can reason about it; with the current implementation, much of
that reasoning is implicit in the code. That combination of model and
code is currently spread between the meta classes and ClassInfo, as well
as several more classes.
* How does access for Realms work? Are they public by default and/or
can they be designated as private, package-private, etc.? Would
protected be allowed?
I did not explicitly define that yet, yes. But since a definition will
be represented as class, it goes with the visibility of the class. And I
guess I should move the declaration style I used before to an explicit
class, where you can apply a modifier as well. Then simply normal class
rules apply.
* What is the compelling use case/reason for a user to want to even
use/know about/understand this feature at all? I would want to avoid
it having the same kind of confusion and/or kind of general disdain/
disinterest at understanding as there is/was around the JVM's
concepts of classpath and modulepath interplay.
More control over the dynamic semantics of a piece of code.
A library can choose which extension methods, categories and MetaClass
behavior its code uses, without implicitly depending on whatever dynamic
environment happens to surround it. An application can do the same when
it wants a particular dynamic behavior or wants to isolate parts of the
application from each other.
Jigsaw mainly restricts what code can access. Realms do not primarily
restrict access. They let the programmer choose which dynamic
capabilities are present. There is isolation, yes, but there is no
*privileged* "inside" that needs to be protected from an "outside".
Think:
My library should behave the same way whether or not the application
happens to have some unrelated extension method or MetaClass
customization installed.
* Are Realms repeatable/stackable (e.g. |@Realm(X) @Realm(Y) def
my_method() { ... }|)? I could see it going either way on allowing
or disallowing this. If allowed, there's then the challenge of
checking for overlaps/conflicting extensions, precedence handling,
and cycle detection that would have to be done at each usage site.
If not allowed, then there's the challenge of how to allow users to
compose Realms and/or extending Realms either that they didn't write
and/or don't have access to.
|@Realm(X) @Realm(Y) def my_method() { ... }| is one usage site only.
But no, I would not allow that.
At a syntax level, I know this will come off really blunt and harsh, I
apologize in advance. I /really/ don't like the shape or design of the
syntax for implementing a Realm's definition at all, as it is stated in
the GEP. It looks very out-of-place and like non-idiomatic Groovy. It
reads /very/ strangely and is awkward to understand at a glance since it
seems like a hybrid of a not-closure closure and half a class
declaration with a weird set assignment/definition thrown in for good
measure. Syntactically, it's just very confusing to read/understand and
I would imagine it will be quite kludge'y to implement at a grammar/
syntax level as is. Again, I don't mean to draw the same parallel to
jigsaw but it comes off as feeling like module-info.java all over again.
Again, apologies for being really blunt and just hating on it.
you will be then surprised to hear that this requires not changes to the
syntax at all. Instead it is a DSL, a local transform changes the
semantics. I would probably now move the block into the annotation and
annotate a class that defines modifier and name. But I doubt that
changes your verdict. I am open for a better version of course. Only
that I do not want this definition to be a runtime construct. The result
should be readable without initializing the class it is based on.
I think it would be simpler, clearer, and more consistent to not get
clever at all with the syntax here and just be super boring about it by
having it be implemented it as a typical plain-old Groovy class like
anything else. Using common semantics like a constructor and a super
class to clearly define the API contract of it all. The compiler could
just scan for any and all classes that extend the super class (or
implement a particular interface as that could be another route to go)
and process those first/separately as environments/realm definitions.
Sometimes simple and boring is best.
The class-scanning part is exactly what I do not want. A realm should be
an explicit semantic dependency, not something the compiler discovers by
scanning the classpath. Otherwise adding an unrelated realm to the
classpath can change compilation semantics, and incremental compilation
becomes harder to reason about.
I also do not want a realm definition to require class initialization or
object construction. The definition is metadata. I want the compiler and
runtime to be able to inspect it directly from the class file, using
reflection or ASM, and obtain the realm token without executing user code.
So a plain class can still be the underlying representation, but the
realm definition itself needs to be declarative and explicitly
referenced rather than discovered by scanning for subclasses.
The class is the discovery mechanism.
At a conceptual level, I like the idea of what the GEP is shooting for
in the broad strokes. There's some details to flesh out and refine. I
think it would be really helpful as well to see a few illustrative
examples and samples of the idea in motion so that someone could get a
sense of what it enables, how it would work, and the value it adds for
them and just getting a bird's eye view of it all.
**Library isolation**
```groovy
MY_LIB = {
extends GROOVY_DYN
extensions MyExtensions
metaclass isolated
}
```
and use `@Realm(MY_LIB)` for its implementation. The library then does
not accidentally acquire extension methods or MetaClass changes from the
application using it.
**Explicit extension dependencies**
Instead of discovering extension methods from everything on the
classpath, a realm explicitly declares them:
```groovy
MY_LIB = {
extensions [MyExtensions, SOME_OTHER_REALM.extensions]
}
```
**Lexical categories**
A category can be represented as a derived realm for a lexical region:
```groovy
use(MyCategory) {
// category semantics apply here
}
```
rather than changing thread-bound dynamic state. This is closer to how
categories are usually intended to be used, and it also gives the
compiler a known semantic environment for those call sites.
**Groovy itself**
The same mechanism allows different parts of Groovy to choose different
dispatch environments. For example, a new MetaClass implementation could
be used in a realm without requiring all of Groovy to use it
immediately. This is also the direction in which I see the realm concept
helping with a more optimized form of dynamic compilation.
Complete example would probably be too much for this mail.
On a meta note, what Groovy version is this tentatively planned to
target? I didn't see any mention of a target version but I would
speculate v8 or v9 and beyond.
well... maybe Groovy 7 is too short. But nothing says that it has to be
complete in version 7.
On a meta meta note, the original email came through just fine on my end
and I even saw it on the mailing list archive too. Not sure that helps
at all or anything but just throwing that out there. Emails/servers
being wonky is one of the many fun caveats of old school mailing list
style development.
I opened https://issues.apache.org/jira/browse/INFRA-28349 for this, and
we are both victims of this.
I hope this is helpful. Thank you for writing this GEP up regardless.
The worst would have been nobody caring to even answer - so thank you
for caring! I think no good idea can be good enough if it cannot
withstand some skepticism.
bye Jochen