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