受约束的接口实现

2022-09-04 21:37:53

在 Haskell(和 Rust 等)中,我可以拥有受其他实例约束的实例:

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'

使用Java接口,我不能。我必须要求 的类型参数总是实现,否则我根本无法实现:PairEqEq<Pair<A, B>>

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);
    }
}

我想有这样的东西:

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

到目前为止,互联网告诉我,Java并不直接支持我想要的功能。尽管如此,我发现这是接口(可重用)可用性的一个相当关键的因素。我想知道是否有大约涵盖相同领域的解决方法或解决方案。


答案 1

我想到的一个规范解决方案是使比较器位于类的外部。这是 Scala 采用的方法,同时在隐式的帮助下使其更易于消化。他们有构建比较器的方法,例如

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

然而,这非常麻烦。Haskell在内部做同样的事情,在引擎盖下传递类型类实现的字典,但是类型类接口和类型推断使它更加愉快。

据我所知,在Java中没有办法声明条件实例。但更 OO 的方法是为允许相等的对创建一个子类:

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

...

再次涉及一些手动过程,因为您需要决定何时使用以及何时使用。但是通过方法重载,我们可以通过声明智能构造函数来使其更易于使用。由于Java总是选择最具体的一个,每当我们需要创建一个对时,我们只使用,Java将选择返回的那个,如果参数正确实现:PairPairEqmkPairPairEqEq

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);
}

完整的示例代码:

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);
    }
}

答案 2

你翻译错了类型类。

类型类与继承无关。它们实现了Ad-hoc多态性,即它们用于扩展已定义的类型的功能。

这个东西可以作为一种模式翻译成Java,但你必须注意,虽然在Haskell中实例是隐式提供的,但在Java中,你必须将它们作为参数显式传递:

interface Eq<A> {
  public boolean eq(A left, A right);
}

class EqInstances {
  public static Eq<Pair<A, B>> pair (final Eq<A> aInstance, final Eq<B> bInstance) {
    return new Eq<Pair<A, B>> {
      public boolean eq(Pair<A, B> left, Pair<A, B> right) {
        return aInstance.eq(left.a, right.a) && bInstance.eq(left.b, right.b);
      }
    };
  }
}

下面介绍如何使用此模式编写多态函数:

boolean someMethodThatUsesAnEqInstance<A>(final Eq<A> aEqInstance, A a1, A a2) {
  return aEqInstance.eq(a1, a2);
}