Constructor Interfaces

Brian Goetz brian.goetz at oracle.com
Wed Jan 25 16:24:06 UTC 2023


I think you may have misunderstood.  We don't use annotations to extend 
the language in this way at all; if you want a new type system feature 
(such as new kinds of bounds on type variables), you have to extend the 
language honestly.  My comments below were related not to the 
annotation-mockup of the feature, but to the actual feature itself.

What you're doing is inventing an entirely new kind of 
implements-interface relation, one which the usual inheritance rule:

     T implements I   S extends T
     ----------------------------
            S implements I

does not apply.

That's not to say that it can't be done, but I think you're dramatically 
underestimating what you're suggesting.

I get it; what you really want to be saying is "gee, it would be nice if 
we could express constraints on type variables that the type being 
substituted provides static methods and/or constructors".  And it would 
be nice, but it's not a new observation, and the answers are complicated.


On 1/25/2023 10:54 AM, Red IO wrote:
> I'm not sure how big of a closing factor this implementation would 
> have. Since the whole implementation is opaque to everyone but the 
> compiler. If implemented that way it would be vital to guard the 
> information carrying field from the user (against reflection for 
> example). If done correctly this would be so opaque that a full 
> implementation change would be possible. The sole requirement would be 
> that you could use the type variable the same way in the new 
> implementation.
> As I was exploring a possible implementation in my last mail I 
> realized that it (by design) is 90% preprocessor. I think I will 
> continue exploring it by trying to implement it using an annotation 
> processor and explore potential problems while using it.
> Of cause the annotation processor will be ugly to use and for 
> development only.
>
> Great regards
> RedIODev
>
> On Wed, Jan 25, 2023, 16:11 Brian Goetz <brian.goetz at oracle.com> wrote:
>
>     This is a good example of a "you can, but you probably shouldn't"
>     language feature.  The power-to-weight ratio isn't favorable; it
>     is a lot of new machinery and concept to move the ball forward a
>     small amount.  And as soon as the ball is moved forward by that
>     amount, we will immediately be confronted by the next thing we
>     can't do, and the solutions are likely to be an increasingly
>     complex sequence of worse power-to-weight ratio ideas. (Careful
>     observers of Java history might note that this phenomenon is
>     especially evident in any proposal surrounding annotations.)
>
>     As Michael K pointed out, other languages have explored more
>     general, but more suitable, answers here; what you're looking for
>     is a witness to conformance to a type class.  (Our friends in C#
>     have pursued something similar through abstract statics; for
>     various reasons, that's less of a good match for Java than for
>     C#.)  This is not a small ask; its significant new complexity, but
>     the power gained is much greater.  If we were to choose to invest
>     in solving problems like this one, that would likely be the path,
>     but this is a big lift and we have other big things on our plate
>     right now.
>
>     As a general note, while it is fun to imagine new language
>     features, language design needs to be a holistic process.  If we
>     did a hundred "point" features like this, what are the chances
>     that the whole would hold together?  If we did this feature, what
>     other potential feature directions are we implicitly foreclosing
>     on?  These are the questions we address ourselves to when choosing
>     what features to consider and not.
>
>
>     On 1/25/2023 2:03 AM, Red IO wrote:
>>     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/20230125/c62db9e9/attachment-0001.htm>


More information about the amber-dev mailing list