Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constrained interface implementation

In Haskell (and Rust, and others) I can have instances that are constrained by other instances:

data Pair a b = Pair a b

instance (Eq a, Eq b) => Eq (Pair a b) where 
    Pair a b == Pair a' b' = a == a' && b == b'

With Java interfaces I can't. I must require the type parameters of Pair to always implement Eq, or I can't implement Eq<Pair<A, B>> at all:

interface Eq<A> {
    public boolean eq(A other);
}

class Pair<A extends Eq<A>, B extends Eq<B>> implements Eq<Pair<A, B>> {
    A a;
    B b;
    public boolean eq(Pair<A, B> other){
        return a.eq(other.a) && b.eq(other.b);
    }
}

I'd like to have something like:

class Pair<A, B> implements Eq<Pair<A, B>> if (A implements Eq<A> && B implements Eq<B>) {...}

So far the Internet has told me that my desired functionality isn't directly supported in Java. Nevertheless I find this a rather critical factor in the (re)usability of interfaces. I'd like to know if there are workarounds or solutions that approximately cover the same ground.

like image 864
András Kovács Avatar asked Sep 25 '15 19:09

András Kovács


People also ask

What is a constrained interface?

Constrained user interfaces restrict user's access ability by not allowing them to request certain functions or information, or to have access to specific system resources. There are three major types of restricted interfaces: Menus and Shells: Database Views.

What would be the purpose of implementing a constrained or restricted interface?

The correct response is restricts user access abilities by only allowing the user to request certain functions or have access to specific system resources. Constrained user interface limits the users environment within the system and access to objects.

What is constraint C#?

Constraints inform the compiler about the capabilities a type argument must have. Without any constraints, the type argument could be any type. The compiler can only assume the members of System.Object, which is the ultimate base class for any .NET type. For more information, see Why use constraints.

What is the purpose of the class constraint on a type parameter?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.


1 Answers

One canonical solution that comes to my mind is to make the comparator external to the class. This is the approach that Scala takes, while making it easier digestible with the help of implicits. And they you'd have methods for constructing comparators, such as

public <A, B> Comparator<Pair<A,B>> pairCmp(Comparator<A> ca, Comparator<B> cb) { ...

This is however quite cumbersome to work with. Haskell does the same thing internally, passing dictionaries of type-classes implementitions under the hood, but the type-class interface together with type inference makes it much more pleasant.

As far as I know, in Java there is no way how to declare conditional instances. But a more OO approach would be to have a sub-class for pairs that allow equality:

class PairEq<A extends Eq<A>, B extends Eq<B>>
  extends Pair<A,B>
  implements Eq<Pair<A, B>> {

...

There is again some manual process involved, as you need to decide when to use Pair and when PairEq. But with method overloading, we could make it easier to use by declaring smart constructors. As Java always picks the most specific one, whenever we need to create a pair, we just use mkPair and Java will pick the one returning PairEq, if the arguments implement Eq appropriately:

public static <A,B> Pair<A,B> mkPair(A a, B b) {
    return new Pair<A,B>(a, b);
}

public static <A extends Eq<A>, B extends Eq<B>> PairEq<A,B> mkPair(A a, B b) {
    return new PairEq<A,B>(a, b);
}

Complete sample code:

interface Eq<A> {
    public boolean eq(A other);
}

public class Pair<A,B> {
    public final A a;
    public final B b;

    public Pair(A a, B b) {
        this.a = a;
        this.b = b;
    }

    public static <A,B> Pair<A,B> mkPair(A a, B b) {
        return new Pair<A,B>(a, b);
    }

    public static <A extends Eq<A>, B extends Eq<B>> PairEq<A,B> mkPair(A a, B b) {
        return new PairEq<A,B>(a, b);
    }
}

class PairEq<A extends Eq<A>, B extends Eq<B>>
    extends Pair<A,B>
    implements Eq<Pair<A,B>>
{
    public PairEq(A a, B b) {
        super(a, b);
    }

    @Override
    public boolean eq(Pair<A,B> that) {
        return a.eq(that.a) && b.eq(that.b);
    }
}
like image 90
Petr Avatar answered Oct 09 '22 15:10

Petr