Constructor Interfaces

Joseph D. Darcy joe.darcy at oracle.com
Tue Jan 31 03:18:44 UTC 2023


To add to what Brian and others have said on this thread, annotation 
processors as first shipped in JDK 5.0 with apt and later standardized 
through JSR 269 in Java SE 6 were intentionally designed to *not* allow 
direct modification of ASTs to avoid language fragmentation, etc.

However, it was acknowledged that at least some subset of the ability to 
modify sources was useful so annotation processors supported the ability 
to generate missing subclasses and *superclasses* during processing. 
Those capabilities are sufficient to implement a properties-like scheme, 
as sketched out in this blog entry:

https://web.archive.org/web/20100410203408/http://blogs.sun.com/darcy/entry/properties_via_annotation_processing

and later productized in projects like:

http://immutables.github.io/immutable.html

HTH,

-Joe

On 1/30/2023 6:36 AM, Brian Goetz wrote:
> Annotations are exactly as (not) powerful as they were designed to 
> be.  JSR 175 (https://www.jcp.org/en/jsr/detail?id=175) -- the effort 
> that gave us annotations -- is called a _metadata facility for the 
> Java Language_, not a _metaprogramming facility_.  This choice was not 
> made because the leaders of that effort were ignorant about 
> metaprogramming; the goal was to leave interpretation of annotations 
> to things like service containers, test frameworks, etc, because 
> annotations are too weak a mechanism for providing language semantics.
>
> As Ron pointed out, it is a very common mistake when imagining 
> possible language features to focus only on "if I had X, what cool 
> code could I write?", and ignore the "if everyone had X, what would be 
> the effect on readability, reliability, and maintainability of the 
> global codebase?"  But understanding the latter is where much of the 
> challenge lies in responsibly evolving a language.
>
>
>
>
>
> On 1/30/2023 5:26 AM, Red IO wrote:
>> After exploring the world of annotation processors trying to 
>> implement test versions of the features, I discovered that annotation 
>> processors can't officially edit the ast and Lombok uses undocumented 
>> hacks to achieve this. This is a bit of a problem for implementing 
>> the test versions that way. My question is why is this disallowed? It 
>> would be a really powerful tool for compile time code execution/ 
>> generation.
>> Great regards
>> RedIODev
>>
>> On Wed, Jan 25, 2023, 20:19 Red IO <redio.development at gmail.com> wrote:
>>
>>     If the type information is an opt in feature it would not violate
>>     that rule. Adding an erasing parameter would still be a non
>>     breaking change. But if you opt in to request the type
>>     information and write logic depending on that information then
>>     this is a braking change to the code anyway since the logic
>>     wouldn't work with a raw type anyway. This could be another
>>     factor to push away from utilizing raw types. It could be a
>>     keyword you add to a parameterized thing to opt in (or implicitly
>>     opt in by utilizing the extended type information) and in the
>>     same act disable the raw version for it. Meaning that it would be
>>     a compile error to use the parameterized thing.
>>
>>     Another option and preserve raw compatibility would be to exclude
>>     the hidden parameter in a raw instance. The downside to this
>>     approach would be that 2 signatures for every method/constructor
>>     that would otherwise include the hidden parameter would be
>>     required. Also for generic classes there would be 2 class layouts
>>     1 with the hidden fields and 1 without.
>>
>>     A completely different approach would be a static map of object
>>     instances to parameter lists. This would require 0 changes in the
>>     class itself. But in case of many generic objects loaded the
>>     performance of this approach is likely catastrophic. Should be
>>     tested if proving viable though. Another challenge is how a
>>     constructor would register the objects extended type information
>>     without an extra parameter. Also this would be difficult to
>>     implement for methods who are so short lived that the type
>>     parameter registration would likely take as long as the method
>>     itself needs to complete.
>>
>>     Personally I would vote for braking with raw types (first
>>     approach) it wouldn't harm existing code (since opt in), would
>>     provide the simplest (and probably fastest) version and the only
>>     downside would be a harder and braking transition for api which
>>     still use raw types and want to utilize the extended type
>>     information (but as I already mentioned if an api wants to opt in
>>     to extended type information on a class; having raw instances of
>>     it would make absolutely no sense anyway).
>>
>>     Great regards
>>     RedIODev
>>
>>     On Wed, Jan 25, 2023, 19:29 <forax at univ-mlv.fr> wrote:
>>
>>         [private]
>>
>>         ------------------------------------------------------------------------
>>
>>             *From: *"Red IO" <redio.development at gmail.com>
>>             *To: *"Remi Forax" <forax at univ-mlv.fr>
>>             *Cc: *"amber-dev" <amber-dev at openjdk.org>
>>             *Sent: *Wednesday, January 25, 2023 3:17:34 PM
>>             *Subject: *Re: Constructor Interfaces
>>
>>             I proposed an idea (to Valhalla) to overcome type erasure
>>             that used the same idea of desugering and a hidden
>>             argument that carries the erased types class variable
>>             inside the generic context, but it was rejected as a to
>>             naive approach or something and they where "already
>>             working on different solutions for some time".
>>
>>
>>         What you have proposed is very similar to what Kotlin does,
>>         pass supplementary type arguments as parameter arguments/fields.
>>         This does not work, the Java spec explicitly says that adding
>>         type parameters, if there were previously none, is a backward
>>         compatible change but adding a supplementary parameter is not
>>         a backward compatible change (especially not a binary
>>         backward compatible change).
>>
>>         That why the current proposed design pass the type arguments
>>         as a side channel not encoded in the type descriptor, see
>>         https://cr.openjdk.java.net/~jrose/values/parametric-vm.pdf
>>
>>         regards,
>>         Rémi
>>
>>
>>             Great regards RedIODev
>>
>>             On Wed, Jan 25, 2023, 14:51 Remi Forax
>>             <forax at univ-mlv.fr> wrote:
>>
>>                 We may need something like this for Valhalla, when we
>>                 will revisit how to constraint the type arguments of
>>                 universal generics.
>>
>>                 The kind of constraints you describe on type
>>                 parameters already exist in C# or TypeScript and was
>>                 more recently introduced in Go, and there is the type
>>                 class of Haskell too.
>>
>>                 regards,
>>                 Rémi
>>
>>                 ------------------------------------------------------------------------
>>
>>                     *From: *"Red IO" <redio.development at gmail.com>
>>                     *To: *"amber-dev" <amber-dev at openjdk.org>
>>                     *Sent: *Wednesday, January 25, 2023 8:03:14 AM
>>                     *Subject: *Constructor Interfaces
>>
>>                     Summary
>>                     -------
>>
>>                     Enable a parameterized class to constrain the
>>                     parameterized type to be constructible with a
>>                     given list of parameters.
>>
>>
>>
>>                     Motivation
>>                     ----------
>>
>>                     It is possible since JDK 8 to get a constructor
>>                     (method) reference of an object. This allowed for
>>                     the creation of an unknown class with a known
>>                     constructor reference. But currently the only way
>>                     to obtain such reference is at call site like this:
>>                     Box<String> stringBox = new Box<>(String::new);
>>
>>                     It is inconvenient for the user to supply the the
>>                     reference themselves and can confuse them as the
>>                     type of the parameter is something like
>>                     Supplier<String> which doesn't require the pased
>>                     reference to be a constructor.
>>                     It also clutters api's like "toArray" which
>>                     requires an IntFunction to be type safe.
>>
>>                     Description
>>                     -----------
>>
>>                     ConstructorInterface
>>                     A ConstructorInterface is a special kind of
>>                     interface similar to a FunctionalInterface. It
>>                     also has similar constraints. It only allows
>>                     abstract constructors and no other abstract
>>                     methods. It can declare multiple constructors
>>                     though. The definition of such interface would
>>                     look similar to this:
>>
>>                     @ConstructorInterface //optional validation like
>>                     FunctionalInterfaces
>>                     public interface DefaultConstructible {
>>                     new();
>>                     new(char[] chars);
>>                     }
>>
>>                     A parameterized type could declare this interface
>>                     as a type bound for its parameter and therefore
>>                     enabling it to be constructed safely. Like this:
>>                     public class Box<E extends DefaultConstructible> {
>>                     public Box() {
>>                     E newElement = new E();
>>                     }
>>                     }
>>                     The containing type is not forced to implement
>>                     the ContructorInterface explicitly. It is
>>                     implicitly implemented if the required
>>                     constructor(s) is(are) present.
>>                     public static void main(String[] args) {
>>                     Box<String> stringBox = new Box<>(); //compiles
>>                     because String has the required constructors.
>>                     Box<java.sql.Date> dateBox new Box<>(); error:
>>                     java.sql.Data does not satisfy the type bound
>>                     DefaultConstructible
>>                     }
>>                     The interface might not be implemented by any
>>                     class, since it doesn't follow the inheritance
>>                     rule that extending classes of those who
>>                     implement it also implement it. This requirement
>>                     comes from the fact that extending classes do not
>>                     necessarily need to have the same constructor
>>                     signature and therefore don't qualify the
>>                     requirements for the interface. Another option
>>                     would be that extending classes of classes that
>>                     implement a constructor interface explicitly are
>>                     also required to supply the necessary constructors.
>>
>>                     class Foo implements DefaultConstructable {
>>                     //both required by the interface
>>                     public Foo() {}
>>                     public Foo(char[] chars) {}
>>                     }
>>
>>                     class Bar extends Foo {
>>                     //the requirement for the required constructors
>>                     is passed down.
>>                     public Bar() {}
>>                     public Bar(char[] chars) {}
>>                     }
>>
>>
>>
>>                     public static <T extends Foo> T createT() {
>>                     return new T();
>>                     }
>>
>>                     public <T extends Foo> T wrapper() {
>>                     return createT();
>>                     }
>>                     This would technically work but would require a
>>                     lot of static analysis to find the real type of T
>>                     to call its constructor.
>>                     Restricting the use of "new T()" to type
>>                     parameters that specify a constructor interface
>>                     directly and only allow those to be resolved with
>>                     a concrete type rather than another type parameter.
>>
>>                     Alternatives
>>                     ------------
>>                     An alternative would be to introduce new syntax
>>                     to restrict the ability of certain constructors
>>                     on a parameter type. Like c# does (but only for
>>                     the default constructor) :
>>                     public static T foo<T>() where T: new() {
>>                     return new T();
>>                     }
>>                     In java:
>>                     public static <T extends new()> T foo() {
>>                     return new T();
>>                     }
>>                     The downside of this approach is obviously the
>>                     introduction of new syntax rather than reusing
>>                     the interface/inheritance syntax.
>>
>>                     Another alternative to this approach could be to
>>                     implement static abstract methods. This would
>>                     allow an interface to mandate a static Factory
>>                     Method. The downside of this approach is that it
>>                     requires the parameter class to actually
>>                     implement the interface and the concept of type
>>                     erasure would need to be addressed for static
>>                     abstract methods to work. In contrast the
>>                     ConstructorInterface enables every class that
>>                     matches its contract to pass the type bound.
>>
>>
>>
>>                     Risks and Assumptions
>>                     ---------------------
>>
>>                     As mentioned before the restriction the interface
>>                     is giving on a type bound is different to normal
>>                     interfaces, it restricts by its containing
>>                     abstract constructors not by the type itself. It
>>                     also makes use of the new operator on type
>>                     variables.
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230130/779eaaca/attachment-0001.htm>


More information about the amber-dev mailing list