2

I want to implement the Either monad in Java. With the following code, I get the error Erasure of method Either(V) is the same as another method in type Either<T,V> I don't see how T and V are type erased to the same type so that it causes an error.

public class Either<T, V> {
    private T t;
    private V v;

    public Either(T t) {
        this.t = t;
    }

    public Either(V v) {
        this.v = v;
    }
}

Related posts, but not duplicates:

The following posts address two constructors, one with Collection<T> and another with Collection<V>. The problem is that both parameters to the constructors will be type erased to Collection and therefore they have the same type signature.

My post is not a duplicate since the primary types are different.

The following post addresses methods, one with compareTo(Real r) and another with compareTo(Object o)

My post is not a duplicate since the types T and V are not related, at least not in a way that I see.

Questions

  • Why does this type-erasure error occur?

  • How do I resolve this error to implement the Either monad in Java?

Little Helper
  • 1,870
  • 3
  • 12
  • 20

4 Answers4

1

You could introduce a second parameter of different types into each constructor and Java will be fine with that:

public class Either<T, V> {
    public enum Left { Left }
    public enum Right { Right }
    private T t;
    private V v;

    public Either(T t, Left l) {
        this.t = t;
    }

    public Either(V v, Right r) {
        this.v = v;
    }
}

This is reasonable, because now this second parameter can be used as a discriminator.

What struck me was to discover that Java was perfectly able to infer which constructor to call even with this code:

    new Either<Integer,Exception>(new Integer(2),null);
    new Either<Integer,Exception>(new Exception(),null);

So, why was it being so picky about Erasure of method Either(V) being the same as another method in type Either in the first place? I don't know.

What I do know is that I didn't like the idea of always having to pass an extra useless argument to my constructors. So I resorted to a workaround using Java varargs:

public class Either<T, V> {
    public enum Left { Left }
    public enum Right { Right }
    private T t;
    private V v;

    public Either(T t, Left... l) {
        this.t = t;
    }

    public Either(V v, Right... r) {
        this.v = v;
    }
}

Of course, now you could call the constructor like this:

    new Either<Integer,Exception>(new Integer(2),null);
    new Either<Integer,Exception>(new Exception(),null,Right,nul);

And I won't object to it as far as I don't have to read your code.
But the benefit is that you can now build your Either instances like this:

    new Either<Integer,Exception>(new Integer(2));
    new Either<Integer,Exception>(2);
    new Either<Integer,Exception>(new Exception());

You can find a complete, immutable implementation of this type in:
https://github.com/fejnartal/Either-JavaType

0

Apparently, it is the same problem as Implementing Comparable, compareTo name clash: "have the same erasure, yet neither overrides the other" , except my question has a flavor of generics.

The generic types cannot be resolved on runtime since they have been translated to Object. Therefore, public Either(T t) -> public Either(Object t) and public Either(V v) -> public Either(Object v), which have the same type signature.

To solve the problem, one can do the following:

public class Either<T, V> {
    private T t;
    private V v;

    private Either(T t, V v) {
        this.t = t;
        this.v = v;
    }

    public static Either instaceOfT(T t) {
        return new Either<>(t, null);
    }

    public static Either instaceOfV(V v) {
        return new Either<>(null, v);
    }
}
Little Helper
  • 1,870
  • 3
  • 12
  • 20
  • 1
    Someone else has developed such a class: http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/data/Either.html . So you might want to take a look and see how they've implemented it. – Klitos Kyriacou Nov 15 '17 at 12:36
  • 1
    You uh, forgot to assign your `v` there. – Rogue Nov 15 '17 at 15:02
  • I should have known better than writing the code directly into SO. Well spotted. – Little Helper Nov 15 '17 at 15:08
  • For future references, the source code for Either in Functional Java is available here: https://github.com/functionaljava/functionaljava/blob/master/core/src/main/java/fj/data/Either.java – Little Helper Jul 18 '18 at 14:08
0

In case you can use Bounded Types on your generic type Either, this will overcome the erasure issue. Although, I understand that in most cases you would not like to restrict such a class, but define it in a quite generic way, so it can be reused anywhere.

public class Either<T extends String, V extends Object> {

private T t;
private V v;

private Either(T t, V v) {
    this.t = t;
    this.v = v;
}

public static <T extends String, V extends Object> Either<T, V> either(T t) {
    return new Either<T, V>(t, null);
}

public static <T extends String, V extends Object> Either<T, V> either(V v) {
    return new Either<T, V>(null, v);
}

}
0

Both T and V are type-erased to Object, so your two constructors have identical type-signatures when compiled.

We can avoid this by following the advice from Effective Java: Use overloading judiciously, and prefer static factory methods to public constructors.

public class Either<T, V> {
    private T t;
    private V v;

    private Either(T t, V v) {
        this.t = t;
        this.v = v;
    }

    public static <L, R> Either<L, R> left(L leftValue) {
        return new Either(leftValue, null);
    }

    public static <L, R> Either<L, R> right(R rightValue) {
        return new Either(null, rightValue);
    }
}

Method overloads with the same number of arguments usually lead to ambiguity, especially in the face of generics. By avoiding those and exposing different named methods instead, you can keep the compiler and users of your API happy.

MikeFHay
  • 8,562
  • 4
  • 31
  • 52