Constructor Interfaces

Red IO redio.development at gmail.com
Wed Jan 25 16:53:29 UTC 2023


I know that the final feature cannot be implemented with annotations. I
also wasn't thinking of implementing the user facing sugar for this feature.
The Anotation processor would explore the hidden part like the hidden data
field and the creation of the call site expansion.
The different notion of interface is a part of my idea, that is not
required. It can be switched out with any other syntax carrying the same
information. Like a where clause for example:
class Box<T> where T satisfies new() & new(char[]) {}
But the concrete user facing api is secondary.
The idea of the special interface was an approach to implement the new
feature without reserving new keywords and syntax. (at least as little as
possible). The current code that handles generic bounds wouldn't even need
to change. Since the constructor interface preprocessor logic would remove
the bound while parcing the bound. (of course it would need to run before)
But this wouldn't solve user confusion since it looks like an interface
bound it really isn't one. Maybe a mixed approach with a contextual keyword
would be better for clarity, like:
class Box<T extends Foo satisfies DefaultConstructable> {}

What I will now do first is explore the inner workings of the preprocessing
and the limitations of the approach. This will surface problems and or use
cases now not obvious.

Great regards
RedIODev

On Wed, Jan 25, 2023, 17:24 Brian Goetz <brian.goetz at oracle.com> wrote:

> 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/62902284/attachment-0001.htm>


More information about the amber-dev mailing list