我可以用Java定义Negatable接口吗?

q35jwt9p  于 2023-01-04  发布在  Java
关注(0)|答案(4)|浏览(121)

问这个问题是为了澄清我对类型类和更高级类型的理解,我并不是在Java中寻找解决方法。
在 haskell ,我可以这样写

class Negatable t where
    negate :: t -> t

normalize :: (Negatable t) => t -> t
normalize x = negate (negate x)

假设Bool有一个Negatable的示例,

v :: Bool
v = normalize True

一切都很好。
在Java中,似乎不可能声明一个合适的Negatable接口,我们可以写:

interface Negatable {
    Negatable negate();
}

Negatable normalize(Negatable a) {
    a.negate().negate();
}

但是,与Haskell中不同的是,如果不进行强制转换,以下代码将无法编译(假设MyBoolean实现Negatable):

MyBoolean val = normalize(new MyBoolean()); // does not compile; val is a Negatable, not a MyBoolean
    • 是否有一种方法可以在Java接口中引用实现类型,或者这是Java类型系统的一个基本限制?**如果是限制,是否与更高级类型的支持有关?我认为没有:看起来这是另一种限制。2如果是这样,它有名字吗?

谢谢,如果问题不清楚请告诉我!

2uluyalo

2uluyalo1#

实际上,是的。不是直接地,但是你可以这样做。简单地包括一个泛型参数,然后从泛型类型派生。

public interface Negatable<T> {
    T negate();
}

public static <T extends Negatable<T>> T normalize(T a) {
    return a.negate().negate();
}

您可以这样实现此接口

public static class MyBoolean implements Negatable<MyBoolean> {
    public boolean a;

    public MyBoolean(boolean a) {
        this.a = a;
    }

    @Override
    public MyBoolean negate() {
        return new MyBoolean(!this.a);
    }

}

实际上,Java标准库正是使用这种技巧来实现Comparable

public interface Comparable<T> {
    int compareTo(T o);
}
fquxozlt

fquxozlt2#

一般来说,没有。
你可以使用一些技巧(如其他答案中所建议的)来实现这个功能,但是它们不能提供Haskell类型类所提供的所有保证,具体来说,在Haskell中,我可以定义一个如下的函数:

doublyNegate :: Negatable t => t -> t
doublyNegate v = negate (negate v)

现在我们知道doublyNegate的参数和返回值都是t,但是Java的等价物是:

public <T extends Negatable<T>> T doublyNegate (Negatable<T> v)
{
    return v.negate().negate();
}

不会,因为Negatable<T>可以由另一种类型实现:

public class X implements Negatable<SomeNegatableClass> {
    public SomeNegatableClass negate () { return new SomeNegatableClass(); }
    public static void main (String[] args) { 
       new X().negate().negate();   // results in a SomeNegatableClass, not an X
}

这对于这个应用程序来说并不是特别严重,但是对于其他Haskell类型类,例如Equatable,确实会造成麻烦。如果不使用一个额外的对象,并在我们发送需要比较的值的任何地方发送该对象的示例,就没有办法实现Java Equatable类型类,(例如:

public interface Equatable<T> {
    boolean equal (T a, T b);
}
public class MyClass
{
    String str;

    public static class MyClassEquatable implements Equatable<MyClass> 
    { 
         public boolean equal (MyClass a, MyClass b) { 
             return a.str.equals(b.str);
         } 
    }
}
...
public <T> methodThatNeedsToEquateThings (T a, T b, Equatable<T> eq)
{
    if (eq.equal (a, b)) { System.out.println ("they're equal!"); }
}

(In事实上,这正是Haskell实现类型类的方式,但它隐藏了传递给您的参数,因此您不需要弄清楚将哪个实现发送到哪里)
尝试仅使用普通的Java接口来实现这一点会导致一些违反直觉的结果:

public interface Equatable<T extends Equatable<T>>
{
    boolean equalTo (T other);
}
public MyClass implements Equatable<MyClass>
{
    String str;
    public boolean equalTo (MyClass other) 
    {
        return str.equals(other.str);
    }
}
public Another implements Equatable<MyClass>
{
    public boolean equalTo (MyClass other)
    {
        return true;
    }
}

....
MyClass a = ....;
Another b = ....;

if (b.equalTo(a))
    assertTrue (a.equalTo(b));
....

由于equalTo确实应该被对称定义,你可能会认为如果if语句被编译了,Assert也会被编译,但是它没有,因为MyClass不等同于Another,尽管相反的情况是正确的,但是对于Haskell Equatable类型类,我们知道如果areEqual a b工作,则areEqual b a也有效。[1]
接口相对于类型类的另一个限制是,类型类可以提供一种方法来创建一个值,该值在没有现有值的情况下实现类型类(例如,Monadreturn运算符),而对于接口,您必须已经具有该类型的对象才能调用其方法。
你问这个限制有没有名字,但我不知道,这只是因为类型类实际上不同于面向对象的接口,尽管它们有相似之处,因为它们是以这种根本不同的方式实现的:一个对象是它的接口的一个子类型,因此直接携带接口的方法的副本,而不修改它们的定义,而一个类型类是一个单独的函数列表,每个函数都是通过替换类型变量定制的。在类型和具有该类型示例的类型类之间没有子类型关系(Haskell Integer不是Comparable的子类型,例如:简单地存在Comparable示例,每当函数需要能够比较其参数并且那些参数碰巧是整数时,可以传递该示例)。
[1]:Haskell ==运算符实际上是使用类型类Eq实现的......我没有使用它,因为Haskell中的运算符重载可能会让不熟悉阅读Haskell代码的人感到困惑。

uyto3xhc

uyto3xhc3#

我对这个问题的理解是,

我们如何使用Java中的类型类实现ad-hoc多态性?

你可以在Java中做一些非常类似的事情,但是没有Haskell的类型安全保证--下面给出的解决方案可能会在运行时抛出错误。
下面是您可以如何做到这一点:
1.定义表示类型类的接口

interface Negatable<T> {
  T negate(T t);
}

1.实现一些机制,允许你为各种类型 * 注册类型类的示例 *。这里,静态的HashMap可以做到:

static HashMap<Class<?>, Negatable<?>> instances = new HashMap<>();
static <T> void registerInstance(Class<T> clazz, Negatable<T> inst) {
  instances.put(clazz, inst);
}
@SuppressWarnings("unchecked")
static <T> Negatable<T> getInstance(Class<?> clazz) {
  return (Negatable<T>)instances.get(clazz);
}

1.定义normalize方法,该方法使用上述机制基于传递对象的运行时类获取适当的示例:

public static <T> T normalize(T t) {
    Negatable<T> inst = Negatable.<T>getInstance(t.getClass());
    return inst.negate(inst.negate(t));
  }

1.注册各种类的实际示例:

Negatable.registerInstance(Boolean.class, new Negatable<Boolean>() {
  public Boolean negate(Boolean b) {
    return !b;
  }
});

Negatable.registerInstance(Integer.class, new Negatable<Integer>() {
  public Integer negate(Integer i) {
    return -i;
  }
});

1.用它!

System.out.println(normalize(false)); // Boolean `false`
System.out.println(normalize(42));    // Integer `42`

主要的缺点是,正如已经提到的,类型类示例查找可能在运行时失败,而不是在编译时使用静态散列Map也不是最佳的,因为它带来了共享全局变量的所有问题,这可以通过更复杂的依赖注入机制来减轻。从其他类型类示例自动生成类型类示例,可能需要更多的基础设施(可以在库中完成),但原则上,它使用Java中的类型类实现了ad-hoc多态性。
完整代码:

import java.util.HashMap;

class TypeclassInJava {
  
  static interface Negatable<T> {
    T negate(T t);

    static HashMap<Class<?>, Negatable<?>> instances = new HashMap<>();
    static <T> void registerInstance(Class<T> clazz, Negatable<T> inst) {
      instances.put(clazz, inst);
    }
    @SuppressWarnings("unchecked")
    static <T> Negatable<T> getInstance(Class<?> clazz) {
      return (Negatable<T>)instances.get(clazz);
    }
  }

  public static <T> T normalize(T t) {
    Negatable<T> inst = Negatable.<T>getInstance(t.getClass());
    return inst.negate(inst.negate(t));
  }

  static {
    Negatable.registerInstance(Boolean.class, new Negatable<Boolean>() {
      public Boolean negate(Boolean b) {
        return !b;
      }
    });
  
    Negatable.registerInstance(Integer.class, new Negatable<Integer>() {
      public Integer negate(Integer i) {
        return -i;
      }
    });
  }

  public static void main(String[] args) {
    System.out.println(normalize(false));
    System.out.println(normalize(42));
  }
}
slwdgvem

slwdgvem4#

你要寻找的是泛型加上自类型。自类型是泛型占位符的概念,它等同于示例的类。
然而,java中不存在自类型。
您可以接近泛型,但它很笨拙:

public interface Negatable<T> {
    public T negate();
}

然后

public class MyBoolean implements Negatable<MyBoolean>{

    @Override
    public MyBoolean negate() {
        //your impl
    }
    
}

对实施者的一些影响:

  • 它们必须在实现接口时指定自己,例如MyBoolean implements Negatable<MyBoolean>
  • 扩展MyBoolean需要再次覆盖negate方法。

相关问题