Trick zur Umgehung von Type-Erasure bei Either


#1

Ich habe gerade einen interessanten Vorschlag gesehen, um Type-Erasure bei Either (Summen- oder Union-Typ) zu umgehen. Üblicherweise hat man Unterklassen oder statische Factories für den Left und Right-Fall, aber bei dieser Version schreibt man einfach new Either<Integer,String>("foo"), und ist auch in der Lage, den richtigen Fall (hier: Right) typsicher zu recovern.

Der Trick funktioniert über die kreative Verwendung von Varargs:

public final class Either<T1,T2> {
	public enum L { Left }
	public enum R { Right }
	private enum LR { Left, Right }

	public static interface Alternative<T1, T2> {
		void when(T1 t, L... _);
		void when(T2 t, R... _);
	}

	private final T1 left;
	private final T2 right;
	private final LR discriminator;

	public Either(T1 left, L... _) {
		this.left = left;
		this.right = null;
		this.discriminator = LR.Left;
	}

	public Either(T2 right, R... _) {
		this.left = null;
		this.right = right;
		this.discriminator = LR.Right;
	}

	public void match(Alternative<T1, T2> matcher) {
		switch(discriminator) {
		case Left: matcher.when(left); break;
		case Right: matcher.when(right); break;
		}
	}
}

Hier das Repo von Fernando J. N. Talavera mit Tests: https://github.com/fejnartal/Either-JavaType

Eine interessante Idee, die sich vielleicht auch bei ähnlich gelagerten Problemen einsetzen lässt. Allerdings läuft man in Probleme, wenn man ein Either braucht, bei die Typen von Left und Right gleich sind.


#2

Hm. “Trickreich” ja. Aber ein bißchen Blut spritzt einem da schon aus den Augen - und dass sowas wie

Either<Integer, String> e1 = new Either<Integer,String>(1, L.Left, L.Left, L.Left);

möglich ist, finde ich eher häßlich. Sind static Factories so schlimm?

EDIT: Ahja, ein bißchen was zu der häßlichen Möglichkeit sagt er auch selbst in https://stackoverflow.com/a/48711573/3182664

I won’t object to it as far as I don’t have to read your code.

*räusper* ja gut…


#3

Auf jeden Fall sollte man wissen, dass es diese Option gibt (z.B. wenn man eine DSL schreibt).


#4

Nun, ich finde es etwas “unbehaglich” (d.h. “unsauber”) wenn man so eine dunkle Ecke der Sprache (d.h. das (für Java-Verhältnisse! vgl. etwa C++ template overload resolution ) sehr diffizile Varargs-Verhalten so ausnutzt, dass es zwar das erlaubt, was man eigentlich will, aber damit auch eine CanOfWorms falscher Verwendungsmöglichkeiten aufmacht. (Und wenn man etwas falsch verwenden kann, dann wird es auch falsch verwendet…)


#5

Eine Verbesserungsmöglichkeit wäre, statt der Enums public Klassen mit privaten Konstruktoren zu verwenden. Dann wäre das einzige, was man bei den Varargs angeben könnte null.

Allerdings fände ich es auch schöner, wenn Java erlauben würde, “auf Wunsch” die Typinformationen automatisch mitzugeben (etwa wie das über Manifests in Scala möglich ist).


#6

Ich finde die Lösung auch nicht schön, aus 2 Gründen:

  1. Der Diamond-Operator funktioniert nicht .Either<String, Integer> either = new Either<>("Foo"); kompiliert nicht.

  2. Es kompilert nicht, wenn beide Parameter vom selben Typen sind.