在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接口我不能。我必须要求Pair
的类型参数始终实现Eq
,否则我根本无法实现Eq<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中并不直接支持我所需的功能。尽管如此,我发现这是接口(重新)可用性的一个相当关键的因素。我想知道是否有大致覆盖相同理由的变通方法或解决方案。
答案 0 :(得分:7)
我想到的一个规范解决方案是让比较器在类外部。这是Scala采用的方法,同时在implicits的帮助下更容易消化。他们有你构建比较器的方法,比如
public <A, B> Comparator<Pair<A,B>> pairCmp(Comparator<A> ca, Comparator<B> cb) { ...
然而,这是非常麻烦的。
Haskell在内部执行相同的操作,将dictionaries of type-classes implementitions传递给引擎盖,但类型类接口和类型推断使得它更加愉快。
据我所知,在Java中,如何声明条件实例是没有办法的。但是更多的OO方法是为了允许相等的对子具有子类:
class PairEq<A extends Eq<A>, B extends Eq<B>>
extends Pair<A,B>
implements Eq<Pair<A, B>> {
...
还需要一些手动流程,因为您需要决定何时使用Pair
以及何时使用PairEq
。但是通过方法重载,我们可以通过声明智能构造函数来使它更容易使用。由于Java始终选择最具体的一个,每当我们需要创建一对时,我们只使用mkPair
,如果参数适当地实现PairEq
,Java将选择返回Eq
的那个: / p>
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);
}
}
答案 1 :(得分:3)
你正在翻译错误的类型。
类型类与继承无关。它们实现 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);
}
答案 2 :(得分:2)
嗯......不。
但是,如果Pair<A,B>
同时Eq<Pair<A,B>>
,我们可以使用便捷方法将“A,B
”投射到Eq
。这需要在使用站点上执行额外的步骤,并且程序员必须在需要时进行显式类型转换;但那可能不算太糟糕
class Pair<A, B>
{
A a;
B b;
}
static
<A extends Eq<A>, B extends Eq<B>>
Eq<Pair<A,B>> cast(Pair<A,B> p1)
{
return p2->( p1.a.eq(p2.a) && p1.b.eq(p2.b) );
}
--
Pair<X,Y> pair = ...;
Eq<Pair<X,Y>> eq = cast(pair);
最好,我们喜欢将cast方法作为实例方法,例如
Eq<Pair<X,Y>> eq = pair.asEq();
但是,除非X,Y
满足约束,否则我们不希望编译它。一种强制执行的方法
class Pair<A, B>
{
A a;
B b;
<A2 extends Eq<A>, B2 extends Eq<B>>
Eq<Pair<A2,B2>>
asEq()
{
return p2->( p2.a.eq(a) && p2.b.eq(b) );
}
}