1

In my Java application I created methods that return Either<String, T> objects.

This is because in some places I invoke these methods as the parameter of (3rd party) methods that expect a String parameter as input. While in other places I invoke these methods as the parameter of (3rd party) methods that expect some other parameter type (T) as input.

So depending on the place where I invoke the methods that I created, the code looks like:

their.thirdPartyExpectsString(my.calculateEither().getLeft());
their.thirdPartyExpectsString(my.calculateEither() + "");

or

their.thirdPartyExpectsDouble(my.calculateEither().getRight());

(I defined Either.toString() as Either.getLeft()).

Pay attention, I cannot change the 3rd party code (anyway not without bytecode manipulation), and I would like to keep my design in which I return Either from my methods.

Is there a way to simplify my code and make it look like

their.thirdPartyExpectsString(my.calculateEither());
their.thirdPartyExpectsDouble(my.calculateEither());

I.e., not having to add the getLeft()/getRight() or + "" all the time?

Actually, it does not bother me much if I will have to do

their.thirdPartyExpectsDouble(my.calculateEither().getRight());

because I don't have to do it often. But I would like to get rid of the need to call getLeft() or + "" when my.calculateEither() returns a Left (a String).

Given an either, it's not hard to see if it represents Right or Left, simply by checking which side has a null.

But the problem is with the type conversion, i.e. the compilation error when thirdPartyExpectsString() expects a String but gets an Either.

I was able to catch the return value of my.calculateEither() by AspectJ but I could not see a way how to use something like @AfterReturning advice to make the compiler understand that I want to return my.calculateEither().getLeft(), i.e a String....

Any ideas?

rapt
  • 11,810
  • 35
  • 103
  • 145
  • I'm not really sure understood the issue, but how about using generic method instead of Either? ex.) public T calculate() {...} – Satoshi Mar 12 '16 at 03:27
  • Your `Either` wrapper is custom class? Then it's easy to introduce implicit conversion to double. That would be awkward with util.Either. – som-snytt Mar 12 '16 at 03:49
  • @Satoshi What I described as `my.calculateEither()` is already a generic method, with a generic parameter T (since String is a fixed type). But anyway with your signature, regardless of where inside T you hide the String value - because you call it where a String parameter is expected, you will have to do something like `t.getString()` or `t + ""`. – rapt Mar 12 '16 at 03:49
  • @som-snytt This application is written in Java. :( – rapt Mar 12 '16 at 03:51
  • 1
    This whole design idea is broken IMO. If you do not want type safety, do not use Java. And as much as I love AOP, this is not an appropriate reason to use it. – kriegaex Mar 12 '16 at 23:38
  • @kriegaex Generally you are right. In this specific case I mitigate the type safety issue by making sure automatically that each `Either` instance is used only in the expected methods. – rapt Mar 13 '16 at 18:43

2 Answers2

1

Add the following method to your implementation of the Either class:

@SuppressWarnings("unchecked")
public <T> T whichever() {
    return (T) (isLeft() ? getLeft() : getRight());
}

Note that I'm purposefully ignoring the warning about the unchecked cast, and indeed will cause a ClassCastException if you use it in a place where the external API you interface with expects a left value but you invoke it on an Either instance which contains a right value. This method will do an implicit cast based on where you use it. It will cast to a T type where you pass it to another method which expects an argument of type T or you try to assign the method return value to a variable of type T.

So the following demo code:

Either<String, Double> containsString = Either.<String, Double>left("first");
Either<String, Double> containsDouble = Either.<String, Double>right(2d);

their.expectsString(containsString.whichever());
their.expectsDouble(containsDouble.whichever());
their.expectsDouble(containsString.whichever());

will work well in the first invocation and will cause a ClassCastException in the third invocation, just an one would expect, because we consider it as an illegal use case.

In conclusion, it's nice to know that it will work in all places where the T type to which we are implicitly casting is assignable to the actual value contained by the Either object. Unfortunately, you will only find out at run time, should this not be the case.

Nándor Előd Fekete
  • 6,988
  • 1
  • 22
  • 47
  • Wow, a really clever maneuver. It's almost implicit type conversion. :) Bravissimo! – rapt Mar 13 '16 at 14:55
  • @rapt Yea, that's the most I could come up with. I was thinking about AspectJ based solutions as well, but I couldn't see a way to do it. It might be possible with direct bytecode manipulation though. – Nándor Előd Fekete Mar 13 '16 at 14:58
  • For me, your idea is better than the AspectJ way. Since it does not require complicating the compilation process. As for the type safety issue - well, in this specific application the creation of `Either` as either `Left` or `Right`, as well as where to use them, is done automatically, so the benefit of the simpler code outweighs the risk of misuing my code. I also posted a follow up on this topic http://stackoverflow.com/questions/35974005 – rapt Mar 13 '16 at 18:38
  • It actually has a name:) https://www.quora.com/Why-didnt-Java-support-adding-methods-to-objects-at-runtime – rapt Mar 25 '16 at 21:33
0

Add a helper method. Since you cannot add new methods to their, you have to add a static to a class of your own.

Pseudo-code:

public static void thirdParty(Their their, Either either) {
    if (either.isLeft())
        their.thirdPartyExpectsString(either.getLeft());
    else
        their.thirdPartyExpectsDouble(either.getRight());
}

You can now call:

MyHelper.thirdParty(their, my.calculateEither())
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • It would be difficult in my case since `thirdPartyExpectsDouble` and other methods in `Their` also return `Their`, and `Their` has a many such methods. My calls look like `their.expectsString_1(..).expectsString_4(..).expectsString_2(..).expectsString_9(..)...` So my Helper class would be very large. – rapt Mar 12 '16 at 03:33