Value.Default(SomeClass.class)
I'd love this for example. Yes, I know this implementation isn't immutable, I'm going to write another ticket about this.
@Value.Default(ArrayDequeue.class)
abstract Queue<Foo> foo()
which would be equivalent to
@Value.Default
Queue<Foo> foo() {
return new ArrayDequeue<>();
}
I think this is both terser and more readable to the layman that may not understand what happens with immutables. Especially when the superclass might call this. and do mutations.
If I put a "language designer" hat on, I would say, that return new ArrayDequeue<>(); Is very natural and easy way to express instantiation of ArrayDequeue then using annotation. At the same time this (@Default(Instance.class)) might an alternative useful for record defaults and so can be shared with regular immutable value types. I'm a bit exhausted with the preparation of 3.11 release, so this definitely not something we can do right away.
By the way the first comment I got from a developer was that the value was always going to be the same thing based on the method return. I then of course explain to them what value.default did but... I don't think this is so obvious from the perspective of people not intimately familiar with the library.
You're right in that there's little if anything natural in how magic annotation like @Default will change a semantic of a method to provide the default on construction and save it in a field instead of returning computed value on each call. All these meta-programming using annotations and annotation processors (or reflection, or bytecode rewriting) suffer from that. I was merely sentimental about if we can express something as regular code (in this case, instantiation of the class instance), we should try to stick to it. However, on a practical side, I was just thinking about "soundness" of the implementation, i.e. If we support just new for no-arg constructor that is fine, but feels incomplete and limited, then we would think how to support factory methods, like (value=Singleton.class, factory="getInstance"), then what about parameters if any, or if we should try to detect factory methods via compile time introspection. See how avaje-record-builder just bluntly (maybe in a good way) direct to use string snippets
@RecordBuilder
public record Defaults(
@DefaultValue("List.of(1,2,3)") List<Integer> list,
@DefaultValue("24") int num,
@DefaultValue("\"string val\"") String str,
@DefaultValue("CustomClass.createDefault()") CustomClass custom) {}
I don't know, if it's good or bad. At least I try to shy away from code/method/field names in strings, we cannot validate those, just compilation will fail when something wrong provided. We have some string expression interpolation in annotation injection where we can insert annotation parameters as a code snippet strings. Is it a precedent, or annotation injection is very technical and potentially brittle and should not be a good primer for a more of a go-to functionality (?). idk
I found this issue because I was looking for a way to define a default for a record that was a custom type (and not one of the new @Value.Default.* types). Since Immutables already supports builder customization with the class Builder extends XBuilder mechanism, I wonder if that mechanism could be extended for support like this. I'm not familiar with the intricacies of annotation processing for generating code, but could something like this be implemented?
@Value.Builder
public record X(List<String> field) {
public static class Builder extends XBuilder {
@Value.Default
protected List<String> fieldDefault() {
return new LinkedList<>();
}
}
}
Immutables could detect the presence of @Value.Default inside the custom Builder and declares protected abstract List<String> fieldDefault(); in the generated version. If the @Value.Immutable/@Value.Builder being processed doesn't have a field attribute, then the annotation processor would fail.