Recursive type parameters for an almost-cyclic type bound
Asked Answered
C

4

9

I have the following two interfaces:

/**
 * A marker interface to denote that an object implements a view on some other object.
 *
 * @param <T>   The type of object that is viewed
 */
public interface View<T extends Viewable<View<T>>> {

}

/**
 * An interface for objects that are viewable via a view.
 * 
 * @param <T>   The type of viewable object
 */
public interface Viewable<T extends View<?>> {
    public void addViewCallback(final T view);

    public void removeViewCallback(final T view);
}

I want to enforce the following:

  • The type parameter of the View (called (a)), should be a Viewable that views upon that view (a).
  • The type parameter of Viewable (called (b)), should be a View, which is viewable via that same viewable (b).

I think I got the bounds done for View, but how am I going to make them work for Viewable? What I got now compiles, but does not offer enough protection.

I cannot, as of now, formulate something that gets accepted which I do not want, I can however formulate what I do want, if that helps:

  • public class Hand implements Viewable<HandView>
  • public interface HandView extends View<Hand>
Coenosarc answered 29/4, 2014 at 12:16 Comment(5)
Could you formulate an instantiation currently allowed that you don't want to allow?Fadeless
@Fadeless Unfortunately no, but I have formulated what I want to have working though, I am worried that Deck implements Viewable<FieldView>, where Deck and Field are not related, would be possible.Coenosarc
I see. The current interfaces' definitions are too restricted to allow what you want, but you don't manage to broaden them without allowing Deck implements Viewable<FieldView>, is that your problem?Fadeless
A marker interface to denote that an object implements a view on some other object. It doesn't seem to be some other object but the object itself. That's what bugs me.Fadeless
Could you please elaborate on the use case itself? It seems that, as @SimonAndréForsberg pointed out, you might not need that much complexity.Fadeless
F
5

As discussed with @SimonAndréForsberg, programming too defensively is often an unnecessary waste of time, and we should trust our own declarations.

However, for the fun of it, here is a solution to enforce all the restrictions you want on class/interfaces declarations at compile time.

Declarations

The View interface:

public interface View<T extends Viewable<T, ?>> {}
  • Here, we just want to ensure that the type parameter is a Viewable.

  • The second type argument does not need restriction, because the declaration of Viewable would not allow a Viewable<T,?> to exist if the ? were not itself a View<T>.

The Viewable interface:

public interface Viewable<T extends Viewable<T,?>, V extends View<T>> {
    public void addViewCallback(final V view);
    public void removeViewCallback(final V view);
}
  • Here, T needs to be declared as a Viewable because we use it as type parameter for View<T> at the end of the line.
  • Same as before, no restriction needed on ? because we say right after that the second type parameter needs to be a View<T> if the first type parameter is a T.

Usage

public class Hand implements Viewable<Hand, HandView> {
    @Override
    public void addViewCallback(HandView view) {}

    @Override
    public void removeViewCallback(HandView view) {}

}

public interface HandView extends View<Hand> {
}
Fadeless answered 29/4, 2014 at 13:12 Comment(1)
@SimonAndréForsberg Thanks! Yeah, that was painful ^^Fadeless
B
7

As your View is a marker interface, and the fact that you as of now haven't formulated anything that gets accepted which you don't want, I question the use of the View interface entirely. You're putting an unnecessary restriction on yourself that leads to nothing but trouble.

Simplify!

/**
 * An interface for objects that are viewable via a view.
 * 
 * @param <T>   The type of viewable object
 */
public interface Viewable<T> {
    public void addViewCallback(final T view);

    public void removeViewCallback(final T view);
}
Buttonwood answered 29/4, 2014 at 12:31 Comment(3)
+1 for the simplification suggestion. However, I think it is simplified too much here, since the OP wants some restriction on the type passed to the methods.Fadeless
@Fadeless I think the OP is putting to many restrictions on himself. I see no need to restrict this at all. It's up to implementations of the interface to pick the type that they choose.Buttonwood
Very true, programming too defensively is sometimes an unnecessary waste of time. The OP should probably trust his own classes delcarations :)Fadeless
F
5

As discussed with @SimonAndréForsberg, programming too defensively is often an unnecessary waste of time, and we should trust our own declarations.

However, for the fun of it, here is a solution to enforce all the restrictions you want on class/interfaces declarations at compile time.

Declarations

The View interface:

public interface View<T extends Viewable<T, ?>> {}
  • Here, we just want to ensure that the type parameter is a Viewable.

  • The second type argument does not need restriction, because the declaration of Viewable would not allow a Viewable<T,?> to exist if the ? were not itself a View<T>.

The Viewable interface:

public interface Viewable<T extends Viewable<T,?>, V extends View<T>> {
    public void addViewCallback(final V view);
    public void removeViewCallback(final V view);
}
  • Here, T needs to be declared as a Viewable because we use it as type parameter for View<T> at the end of the line.
  • Same as before, no restriction needed on ? because we say right after that the second type parameter needs to be a View<T> if the first type parameter is a T.

Usage

public class Hand implements Viewable<Hand, HandView> {
    @Override
    public void addViewCallback(HandView view) {}

    @Override
    public void removeViewCallback(HandView view) {}

}

public interface HandView extends View<Hand> {
}
Fadeless answered 29/4, 2014 at 13:12 Comment(1)
@SimonAndréForsberg Thanks! Yeah, that was painful ^^Fadeless
C
3

I managed to get it working, while taking all other answers here into consideration.

It seems like @Joffrey's answer got me started quite a bit, but then I realised that it is simply not possible this way, as method arguments cannot be covariant.

I also agree that it might be too much work, but that's why it is some experimental stuff, at work with time pressure I might indeed think twice before wasting time on this.

One of the possible solutions is:

/**
 * A marker interface to denote that an object implements a view on some other object.
 *
 * @param <T>   The type of object that is viewed
 */
public interface View<T extends Viewable<T, ? extends View<T>>> {

}

/**
 * An interface for objects that are viewable via a view.
 * 
 * @param <T>   The type of viewable object
 * @param <V>   The concrete view on the viewable object
 */
public interface Viewable<T extends Viewable<T, V>, V extends View<T>> {
    public void addViewCallback(final V view);

    public void removeViewCallback(final V view);
}

Example implementation:

public interface HandView extends View<Hand> {
    public void onCardAdded(final Card card);

    public void onCardPlayed(final int cardIndex);

    public void onCardsSwapped(final int cardIndexOne, final int cardIndexTwo);
}

public class Hand extends AbstractCollection<Card> implements Collection<Card>, Viewable<Hand, HandView> {
    private final List<HandView> views = new ArrayList<>();

    //...

    @Override
    public void addViewCallback(final HandView view) {
        views.add(Objects.requireNonNull(view));
    }

    @Override
    public void removeViewCallback(final HandView view) {
        views.remove(Objects.requireNonNull(view));
    }

    @Override
    public boolean add(final Card card) {
        Objects.requireNonNull(card);
        States.requireFalse(isFull(), "hand is full");
        list.add(card);
        views.forEach(view -> view.onCardAdded(card));
        return true;
    }

    //...
}
Coenosarc answered 29/4, 2014 at 13:20 Comment(1)
I don't think you need the extra restriction on the ? for the delcaration of the View interface. Indeed, the Viewable declaration already limits the second type argument.Fadeless
A
2

The only way this has ever worked for me was to specify both View and Viewable type parameters:

  • public interface View<VB extends Viewable<VB,T>, T extends View<VB,T>>
  • public interface Viewable<VB extends Viewable<VB,T>, T extends View<VB,T>>

Keep in mind that this will require you to create specific interfaces:

  • public interface Hand implements Viewable<Hand,HandView>
  • public interface HandView extends View<Hand,HandView>
Apothegm answered 29/4, 2014 at 12:45 Comment(4)
You might be on to something here, but this does not compile at the moment, which makes it hard to know what you're on to. Incorrect number of arguments for type Viewable; it cannot be parameterized with arguments <View<T>> (Error x2)Buttonwood
Unfortunately it still doesn't compile, Incorrect number of arguments for type View; it cannot be parameterized with arguments <T> here: VB extends Viewable<**View<T>**>Buttonwood
Gah, that's what happens when I try to write code without an IDE... refixed(2).Apothegm
@TassosBassoukos So hard without IDE indeed ^^ Especially for all these interlaced generics!Fadeless

© 2022 - 2024 — McMap. All rights reserved.