manifold-systems / manifold

Manifold is a Java compiler plugin, its features include Metaprogramming, Properties, Extension Methods, Operator Overloading, Templates, a Preprocessor, and more.
http://manifold.systems/
Apache License 2.0
2.37k stars 124 forks source link

Provide a "record" class equivalent using properties (manifold-props) #257

Open rsmckinney opened 3 years ago

rsmckinney commented 3 years ago

Provide a "record" class equivalent using properties (manifold-props).

Rationale:

Java 14 introduced "records", which is basically syntactic sugar to make a final class with all final fields, a constructor reflecting the fields, accessor methods, equals, hashcode, and serialization.

This is nice, but here are some concerns:

Basics:

Now, with Manifold properties we can build a better record:

@record class Book {
  @val ID id;
  @val String title;
  @val Author author;
  @val Kind kind;
}

The @record annotation indicates the Book class is public by default and final. It also instructs the compiler to inject all the necessary record elements including: a constructor matching the final fields, equals() & hashCode(), serialization, etc. To boot, the properties are of course referenced simply by name.

String title = book.title;

Builder

The compiler can enforce required/@NotNull fields by making the record's constructor private and generating an inner Builder class having a constructor matching the required fields and "with" methods matching non-required ones.

Note, I previously considered a @required annotation that could apply to both reference and primitive fields. After further consideration, however, my view is that this crosses over into a higher level of validation that is perhaps more suitable for a schema such as JSON Schema or similar. Or, perhaps not; validation criteria is a tough problem, sticking with just @NotNull for now.

Fluent API

Perhaps follow the same fluent API template as GraphQL, JSON, etc. particularly concerning building, reading, writing, and copying. The @NotNull annotation makes this map to that world nicely.

Other considerations:

Is this the right direction?

Kind of a wild idea, but record classes map so closely to manifold's GraphQL / JSON / XML / etc. world that it screams to be schema based with the same API. So, the question is should Manifold provide something like a light-weight FlatBuffers schema? Or is bolting @record, @required, and other features onto Java enough?

Perhaps, this is not an either/or proposition? Manifold can provide a type manifold for FlatBuffers (and/or similar) too¿

dakotahNorth commented 5 months ago

This would be a fantastic addition to the platform.

As you point out, records disappointing don’t

follow conventional JavaBean standards e.g., the field title has getter title(), not getTitle(). While this is not prohibitive for tooling to discover bean properties, most existing tooling will fail to detect these as property getters.

For that reason, recommend not to call them records, as they would be more powerful than records.

This would also remove all the boiler plate code for DTO objects.

rsmckinney commented 5 months ago

@dakotahNorth I've considered implementing a version of this recently.

The Lombok project is used primarily for the @Data feature, which basically covers what is outlined here, but without actual properties. Although properties via manifold-props eliminates the getter/setter boilerplate it doesn't accomplish the rest of what a DTO or data class provides.

Originally, I avoided implementing the feature because I didn't want to reinvent Lombok's @Data (or be considered a Lombok "ripoff"). But I've come around since Java records were released. In my view Java would benefit from a full-featured data class implemented with properties.

I'm leaning toward building this as an extension to the existing manifold-json component. Basically, instead of defining a JSON schema with JSON, just define it with properties. All the features of JSON objects are free.

Another option is to provide two annotations for the same general feature where @json provides a JSON object and @data provides a simpler, pure Java data class. The latter isn't quite as powerful as the former, but as with many things manifold, folks tend to get nervous around powerful things, and rightly so.

dakotahNorth commented 5 months ago

Originally, I avoided implementing the feature because I didn't want to reinvent Lombok's @Data (or be considered a Lombok "ripoff")

Manifold is far more powerful ... not a Lombok rip-off, it an enhanced replacement.

But I've come around since Java records were released. In my view Java would benefit from a full-featured data class implemented with properties.

Could not agree more!!!

Re implementation ... @dataas a Java data class provides more java flexibility with the opportunity to add additional capabilities to the Java class in a more natural way.

For example ...

  @data
  public class PositionEvent  {

      @val String symbol;
      @val Side side;

      @var long quantity;
      @var long filled;
      @var int price;

      public long getFilledNotional() {
          return filled * price;
      }

      public long getOpenNotional() {
          return quantity * price;
      }

support records having read-write properties (@var)? support non-final record classes? this seems almost necessary for records mapping to entity models

Yes to both please 👍🏻

dakotahNorth commented 3 months ago

Was thinking about this as I continue to use @var more and more.

      @record class Book {
        @val ID id;
        @val String title;
        @val Author author;
        @val Kind kind;
      }

With the @record annotation, suggest that @val would not be necessary (since that is what a record would be by default.)

So for record, all that would be needed is:

    @record class Book {
      ID id;
      String title;
      Author author;
      Kind kind;
    }

And then @data would be var by default:

  @data class Book {
    ID id;
    String title;
    Author author;
    Kind kind;
  }

and then val and var can always provide more fine grain control.

rsmckinney commented 3 months ago

@dakotahNorth Nice.

To the @record / @val idea, we sort of have that already using Java's record class with manifold property inference. But of course it misses out on the wider set of features TBD in the proposed @data class.

I also kind of don't want to overload the "record" concept as it is understood in Javaland. It could be confusing to write @record and allow @var properties in the class. If we do define a @record class, it should probably be immutable. shrug

Anyhow, I like the general idea to simplify data class definition syntax. But I am a smol brain, I'll probably start simple and get the @data class foundation working, then see what we can do about pairing down syntax. Maybe we'll discover something novel in the process :)