Factory methods & the language model

Brian Goetz brian.goetz at oracle.com
Fri Sep 10 18:25:50 UTC 2021


> I'm not particularly interested in settling on a bikeshed color, but am interested in the general mood for pursuing this direction at all. (And not necessarily right away, just—is this a direction we think we'll be going?)
>
> A few observations/questions:
>
> - 'new Foo()' traditionally guarantees fresh instance creation for identity classes. Primitive classes relax this, since of course there is no unique identity to talk about. Would we be happy with a language that relaxes this further, such that 'new Foo()' can return an arbitrary Foo instance (or maybe even null)? Or would we want to pursue a different, factory-specific invocation syntax? (And if so, should primitive classes use it too?)

Let me offer some context from Amber that might be helpful, regarding 
whether we might want "factory" to be a language feature.

Example 1 -- records.  One of the complaints about records is that you 
have to instantiate them through constructors, not factories; over the 
decades, people have come to view exposed constructors as "dirty" and 
therefore would rather see

     List.of(Foo.of(a, b), Foo.of(c, d))

than

     List.of(new Foo(a, b), new Foo(c, d))

This is a cosmetic concern, but it still does elicit "but why" comments 
from developers when they first encounter records.  The reason, of 
course, is that records are a language feature, and therefore have to be 
built on top of other language features. Constructors are a language 
feature, but factories are not, they are just a library structuring 
convention.  If the language had some way to declare factories, we might 
have (but still might not have) encapsulated the constructor and exposed 
a factory instead. (Though, since we'd have to make up a name for the 
factory, and we try to avoid magic naming, we might still have gone with 
ctors.)

Example 2 -- "with" expressions / reconstructors.  A number of 
interesting features come out of the pairing of constructors and 
deconstruction patterns with the same (or compatible) argument lists, 
such as `with` expressions (`point with { x = 3 }`). Handling this 
involves doing multiple overload selection operations, first to find a 
deconstructor that yields the right bindings, and then to find a 
compatible constructor that accepts the resulting exploded state.

Among the many problems of doing this (including the fact that parameter 
names are not required to be stable or meaningful), we also have the 
problem of "what if the class doesn't have a constructor, but a 
factory."  This would require the language to have a notion of factory 
method (e.g., `factory newPoint(int x, int y)`) so the compiler could 
try to match up a compatible factory with the deconstructor.

The same questions apply here -- what constraints do we put on a 
"factory" at the source level?  Implicit null checks on exit?  Do we 
want to pun `new Foo()` with factory invocation, or treat them 
separately?  Many questions (which I'm not trying to answer here.)


I'll just note that there is already an asymmetry in primitive classes 
here, in that the user writes a constructor, but the compiler generates 
a factory in the classfile.




More information about the valhalla-spec-observers mailing list