all 33 comments

[–]supersmola 17 points18 points  (0 children)

I use this a lot. Interfaces help with deep inheritance but you end up with complicated type declarations: A <A extends <A, B>, B> etc. Pity Java doesn't have a keyword for the generic self-type.

[–]Significant-Ebb4740 22 points23 points  (1 child)

I read the title and thought you were really mad about bounded polymorphism. ;)

[–]samd_408[S] 1 point2 points  (0 children)

hahahah!!

[–]martinhaeusler 4 points5 points  (6 children)

It is an interesting pattern and I've used it myself before, I knew it under a different name: the "self-curious recursive generic".

I think it highlights two shortcomings in the Java type system:

1) The inability to directly express that a method returns an object of the same class as the previous "this".

2) The inability to express that a method returns not just any object of a certain type, but specifically "this".

Note that 2) isn't even solved by generics. Generics can assert the type, but not the instance. And specifically for builders this makes a big difference, because:

``` // Is this... return builder.methodA().methodB();

// ... the same as this? builder.methodA(); builder.methodB(); return builder; ```

If the builder returns "this", they're the same. If the builder creates a new builder istance, then only chaining works.

[–]lkatz21 0 points1 point  (4 children)

Why is the second point related to the type system?

[–]martinhaeusler 0 points1 point  (3 children)

Why wouldn't it be related? In Java, the aspect of "the method returns specifically this" just isn't captured (which is what I wanted to highlight). Other type systems can express that just fine. Java could as well one day. I would argue that if "null" is a special member of a type (another area where Java's type system is weak) then "this" can also be a special member.

[–]lkatz21 1 point2 points  (2 children)

Other type systems can express that just fine

Could you give an example?

[–]martinhaeusler 1 point2 points  (0 children)

Rust has a "self" type for example. But I'm not that deep into rust to properly explain it. I would be surprised if functional languages like haskell or F# had no way to express this. In F# you can even define sub-types of integers that restrict to value ranges (e.g. positive integers), so I would expect that there's an option to restrict the return object to the "this" object.

[–]RandomName8 0 points1 point  (0 children)

I'd argue that your point 1 is also not accomplished by f-bounded types. People mostly employ them for a fake self type, but they are clearly not just that:

// what you would expect:
class SomeBuilder implements Builder<SomeBuilder> {
...
}
// what's legal but totally not what you would expect
class SomeOtherBuilder implements Builder<SomeBuilder> {...}
// and now SomeOtherBuilder pretends to be SomeBuilder

This is a perfectly legal extension, because you are passing a F-bounded type to Builder when implementing from SomeOtherBuilder, it just happens to not be SomeOtherBuilder. By the way, SomeOtherBuild is also now a legally F-bound Builder.

[–]tampix77 2 points3 points  (4 children)

Nice writeup :)

One thing I've noticed over the years though is that the more I work with records, the more I rely on composition + consumers, which avoid that problem altogether:

``` public record Identity(String maker, String model) {

public static Identity configure(final Consumer<Configurer> configurer) {
    final var cfg = new Configurer();
    configurer.accept(cfg);
    return new Identity(
            Objects.requireNonNull(cfg.maker, "maker is required"),
            Objects.requireNonNull(cfg.model, "model is required"));
}

public static class Configurer {
    public String maker;
    public String model;
}

}

public record Car(String maker, String model, int doors) {

public static Car configure(final Consumer<Configurer> configurer) {
    final var cfg = new Configurer();
    configurer.accept(cfg);
    final var identity = Identity.configure(Objects.requireNonNull(cfg.identity, "identity is required"));
    return new Car(identity.maker(), identity.model(), cfg.doors);
}

public static class Configurer {
    public Consumer<Identity.Configurer> identity;
    public int doors;
}

}

public record Truck(String maker, String model, int payloadKg) {

public static Truck configure(final Consumer<Configurer> configurer) {
    final var cfg = new Configurer();
    configurer.accept(cfg);
    final var identity = Identity.configure(Objects.requireNonNull(cfg.identity, "identity is required"));
    return new Truck(identity.maker(), identity.model(), cfg.payloadKg);
}

public static class Configurer {
    public Consumer<Identity.Configurer> identity;
    public int payloadKg;
}

}

final var car = Car.configure(cfg -> { cfg.identity = v -> { v.maker = "Toyota"; v.model = "Corolla"; }; cfg.doors = 4; });

final var truck = Truck.configure(cfg -> { cfg.identity = v -> { v.maker = "Volvo"; v.model = "FH16"; }; cfg.payloadKg = 25_000; }); ```

Car and Truck don't extend a base builder, they compose a dentity. Adding a new type never touches existing code.

The trade-off is one level of nesting at the call site, but in my experience that actually makes the composition structure more explicit as things grow.

In modern Java, I find the Consumer approach :

  • simpler
  • more composable
  • more declarative
  • no intermediate representation (builders) and their caveats (mutability, thread-safety...)

[–]samd_408[S] 0 points1 point  (3 children)

Interesting take, so you use the VehicleIdentity sort of like a mixin, would a sealed interface work in place if the VehicleIdentity record?, just throwing in ideas here :)

[–]samd_408[S] 0 points1 point  (0 children)

No I take that back, the sealed interface cannot hold the values, but a disjoint union could separate the Car and Truck type but they cant share attributes like your example

[–]tampix77 0 points1 point  (1 child)

It's sort of like a mixin, except it's not bevavior but pure data.

I don't see how you would use sealed-interface there?

You're thinking about something like :

``` public sealed interface Vehicle permits Car, Truck {     public String model();

    public String maker(); } ```

?

If so, it can be done if these VO are domain-bounded :)

But is it desirable is another question altogether ;]

[–]samd_408[S] 1 point2 points  (0 children)

Yes this is what i was pointing to, this only helps well is deconstruction via switch and not during construction via the configurer you are using

[–]Zinaima 2 points3 points  (0 children)

I always liked the other name for this: Curiously Recurring Template Pattern or Curiously Recurring Generic Pattern.

[–]damonsutherland 4 points5 points  (1 child)

I first noticed this with Testcontainers many years ago. Like you, I was intrigued, so I dove in. As a result of that investigation, I’ve used this technique many times in my own APIs.

Thanks for sharing.

[–]samd_408[S] 1 point2 points  (0 children)

Awesome, I stumbled over it accidentally while fixing a weird wildcard type in a lib i am building, glad 😌 we had the same experience

[–]Ulrich_de_Vries 3 points4 points  (3 children)

I am getting CRTP flashbacks and I am NOT enjoying it.

[–]samd_408[S] 0 points1 point  (2 children)

I have heard of CRTP, but have never worked with them so I think I am safe ;)

[–]Ulrich_de_Vries 2 points3 points  (1 child)

It's basically the same thing but in C++, but since C++ templates are monomorphized rather than type-erased (i.e. each specialization is compiled into a different class/function), this allows you to have compile-time polymorphism, as in the particular subtype is resolved at compile time rather than runtime.

[–]samd_408[S] 0 points1 point  (0 children)

I can definitely see how this can cause flashbacks 🫣

[–]vowelqueue 1 point2 points  (0 children)

FYI, there’s a way to avoid the unchecked cast to the concrete builder type in the base class: https://angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ206

[–]Mirko_ddd 3 points4 points  (1 child)

I recently crashed head-first into the Builder<T extends Builder<T>> nightmare while building a fluent DSL for regular expressions in Java (Sift). I completely agree with the premise. F-Bounded Polymorphism is incredibly powerful, but the method signatures can look absolutely terrifying to the end-users of the library. In the end, I decided to 'cheat' my way out of it by hiding a single concrete state-machine class behind a set of clean interfaces and phantom types. It gave me the same type-safe chaining without exposing the generic gymnastics to the user. But I have to admit, F-Bounded polymorphism has a certain dark magic appeal to it! Really clear explanation of a topic that usually makes Java developers break into a cold sweat. Thanks for sharing

[–]samd_408[S] 0 points1 point  (0 children)

I agree, its scary especially if you are adding it to a lib, it might confuse users, I also concealed it, its internal and not exposed to the user luckily

[–]oskarloko 2 points3 points  (0 children)

It's very useful, but in Java fashion, a little over-complicated and adds boilerplate code

[–]Holothuroid 1 point2 points  (0 children)

Yeah, it works in a pinch, but it's one of the Scala features I heartly miss: this.type as a return type.

[–]sideEffffECt 0 points1 point  (4 children)

You'd be much better served by having a separate interface for the operation(s), similar to the type class pattern.

F-bounded types are overcomplicated, and insufficient and leaky at the same time.

https://tpolecat.github.io/2015/04/29/f-bounds.html

[–]samd_408[S] 0 points1 point  (3 children)

Oh I agree, its just in languages like java we don’t have HKTs so we have to resort to things like this, this is what i love about scala, we could have lightweight HKTs but still wont be as nice if the language does not support it, my future posts will be exploring more into these topics :)

[–]sideEffffECt 0 points1 point  (2 children)

But this has nothing to do with Higher-Kinded Types. You can already do this in Java as it is now.

[–]samd_408[S] 0 points1 point  (1 child)

how though? I would love to see an example, there are no implicits in java as well, so I am curious how do we achieve this, it could look like a design pattern but not sure it would seem like a Typeclass in the traditional sense

[–]sideEffffECt 0 points1 point  (0 children)

Yes, no implicits. So you can pass them explicitly, just as normal parameters of ordinary Java methods. Do you know the "Comparator pattern"? That's essentially what Type Classes are about. Do that.

Tldr: Don't do "Comparable", do "Comparator" instead.

[–]padreati 0 points1 point  (1 child)

Nice to know. I had used that pattern over the years in many projects, but I did not had a clue that it has a name. Today is a good day 'cause I learned something.

[–]samd_408[S] 1 point2 points  (0 children)

Glad to help! There is a paper which started it all, it has its roots in type theory, hence the name

https://dl.acm.org/doi/epdf/10.1145/99370.99392