Trade-Off zwischen Abhängigkeiten und Kompaktheit+Lesbarkeit

In vielen Anwendungsfeldern braucht man eine Funktion, die die Distanz zwischen zwei Objekten berechnet. Das kann man (in der einfachsten Form) sehr leicht als Interface beschreiben:

public interface DistanceFunction<T>
{
    double distance(T t0, T t1);
}

So hatte ich das bei einer Library gemacht, die ich schon vor einer Weile angefangen hatte (bevor Java 8 “verbreitet” war).

Dieses DistanceFunction interface wurde dann an einigen Stellen verwendet. Bestimmte Methoden erwarteten z.B. als Parameter eine solche DistanceFunction, teilweise obwohl sie mit den übrigen Klassen aus dieser Library eigentlich nichts zu tun hatten.

Nun gibt es seit Java 8 die ToDoubleBiFunction, die strukturell sehr ähnlich zu diesem Interface ist. Als eine Art Migrationsschritt hatte ich dann das DistanceFunction interface (rückwärtskompatibel) generalisiert:

public interface DistanceFunction<T> extends ToDoubleBiFunction<T, T>
{
    double distance(T t0, T t1);
    
    @Override
    default double applyAsDouble(T t0, T t1)
    {
        return distance(t0, t1);
    }
}

Ich bin mir aber im Moment nicht sicher, ob es das DistanceFunction interface jetzt überhaupt noch geben sollte. Der Vorteil liegt in einer etwas besseren Lesbarkeit:

double[][] computeDistanceMatrix(
     List<T> objects, 
     DistanceFunction<? super T> d) 
{ 
    ...
    double distance = d.distance(t0, t1);
    ...
}

vs.

double[][] computeDistanceMatrix(
     List<T> objects, 
     ToDoubleBiFunction<? super T, ? super T> d) 
{ 
    ...
    double distance = d.applyAsDouble(t0, t1);
    ...
}

Der Nachteil aber darin, dass man sich für ein einzelnes, ziemlich triviales und durch die Standard-API eigentlich überholtes Interface eine Abhängigkeit zu einer bestimmten Lib einhandelt (und wie einige vielleicht schon mitgekriegt haben bin ich beim Herstellen von Abhängigkeiten immer recht zögerlich … deswegen habe ich schon eine bestimmte Tendenz…)

(Auch wenn ich weiß, dass es schwierig ist, da definitive Aussagen zu machen, solange man die genauen Zusammenhänge und das ganze Drumherum nicht kennt: )

Was würdet ihr machen?

     List<T> objects,
     ToDoubleBiFunction<? super T, ? super T> distanceFunction)
{
    ...
    double distance = distanceFunction.applyAsDouble(t0, t1);
    ...
}```

Ist jetzt sehr arrogant, besonders wenn man sich diverse Flames bezüglich sprechender Variablen hier im Forum vor Augen hält.

[Collectors (Java Platform SE 8 )](http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html)

Wenn ich einen Blick in die Java-Api, insbesondere zur Klasse Collectors werfe, dann fällt mir folgendes auf.

Die erste Methode, `averagingDouble(ToDoubleFunction<? super T> mapper)` hat eine `ToDoubleFunction` mit dem Sprechenden Namen `mapper`. Der Name ist ungemein wichtig!

Eine Funktionales-Interface hätte man machen können, IMapToDouble<T> (ganz sprechender Name, bei dem das I für Ich steht). 

Hätte man etwas gewonnen? Ich glaube nicht. Eher würde man noch tausende zusätzliche Interface-Definitionen in der Standard-API haben.


Ein Problem ist, ganz unbestritten, dass `ToDoubleBiFunction<? super T, ? super T>` in den Augen weh tut.
Es tut weh, weil es noch ungewohnt ist. Lassen wir aber mal ein paar Jahre ins Land verstreichen und geben dem eine Chance, dann sieht man auf einen Blick, dass hier einfach ein Lambda mit zwei Parametern erwartet wird, welches ein Double zurückliefern soll.

```(a,b) -> Math.sqrt(Math.pow(a.getX() - b.getX(), 2) + Math.pow(a.getY() - b.getY(), 2))```

`DistanceFunction<T>` sehe ich nicht unbedingt an was erwartet wird. Dazu muss ich erst hineinschauen. 

Eine IDE hilft hier, erst die Anonyme Klasse zu erstellen, samt zu implementierender Methode und diese dann später in ein Lambda umzuwandeln. Falls gewollt. Aber es wird eine IDE oder das Wissen, bzw. die Zeit benötigt um Nachzusehen was DistanceFunction sein soll.


Angenommen du hättest eine Klasse Node2D. Wo würdest du DistanceFunction implementieren?

```public class Node2DdistanceFunction implements DistanceFunction<Node2D> { ... }```


Oder einfach in Node2D einen Standard hinterlegen. Bei verschiedenen (kartesisch, pythagoras'sch )Varianten, evtl. Node2dDistanceFunctions und dann via Method-Refs arbeiten.

```public class Node2D {

public static ToDoubleFunction<Node2D,Node2D> DISTANCE_FUNCTION = (a,b) -> {return 13d;};

}```


Den Vorteil, den ich also zukünftig erwarte, wenn die Übungsphase bzgl. FunctionalInterface abgeschlossen ist, sofort zu sehen was in einem Framework oder Lib erwartet wird.

Rhetorisch gefragt:
Warum kann ich in Java nicht von Double erben?
Warum verwendet man in Java den Konstruktor `Point2D.Double(double x, double y)` und nicht` Point2D.Double(XValue<Double> x, YValue<Double> y)`?

Nun, ein Kernpunkt (der vielleicht zu dem (zugegeben nicht erwähnten) „Drumherum“ gehört) ist, dass diese Dinger viel „herumgereicht“ werden müssen. Grob im Sinne von

void process(DataSet<Data> dataSet)
{
    DistanceFunction<Data> distanceFunction = dataSet.getDistanceFunction();
    doSomethingWith(distanceFunction);
    someOtherDataSet.setDistanceFunction(distanceFunction);
}

was mit der ToDoubleBiFunction halt schon sehr sperrig aussieht (vor allem, weil die Empfänger immer die ? super’s haben sollten). Dass beide Interfaces durch ein Lambda abgebildet werden könnten ist dabei fast egal.

Das würde ich nicht sagen. Auch wenn deine Ausführungen zu den (Variablen)namen oben vermeintlich nicht zur Frage passten: Sie passen zumindest in dem Sinne, dass eine „Distanzfunktion“ in den Bereichen, um die es hier (primär) geht, eine SEHR prominente Rolle spielen, und damit schon im (Klassen!)namen klargemacht wird, dass das nicht „irgendeine Binäre to-Double-Funktion“ ist, sondern eben eine Distanzfunktion - und nicht etwa ein Ähnlichkeitsmaß…

void setDistanceFunction(DistanceFunction<? super T> d) { ... }
void setSimilarityMeasure(SimialrityMeasure<? super T> s) { ... }

vs.

void setDistanceFunction(ToDoubleBiFunction<? super T, ? super T> d) { ... }
void setSimilarityMeasure(ToDoubleBiFunction<? super T, ? super T> s) { ... }

Diese „Typsicherheit“, die bei Generalisierungen wegfällt, ist aber ein allgemeines Problem.

Interessanterweise war der exakte Anlass für diese Frage (!) der, dass ich vorher diesen Codeblock

    /**
     * Returns the distance function that will be used for the 
     * computation of distances between {@link Node}s 
     * 
     * @return The distance function
     */
    DistanceFunction<Node> getNodeDistanceFunction();

geändert hatte zu

    /**
     * Returns the distance function that will be used for the 
     * computation of distances between {@link Node}s 
     * 
     * @return The distance function
     */
    ToDoubleBiFunction<Node, Node> getNodeDistanceFunction();

und nicht sicher war, ob das eine „Verbesserung“ war. (An vielen Stellen wurde dementsprechend vorher eine DistanceFunction erwartet, die jetzt in die ToDoubleBiFunction geändert werden mußte, mit ihrer sperrigen Signatur…)

Innerhalb der Implementierung dieses Interfaces wird die Instanz entsprechend erstellt,

                this.nodeGridDistanceFunction = 
                    new NodeGridDistanceFunction(
                        IntTupleDistanceFunctions.manhattan());
// bzw.
                this.nodeGridDistanceFunction = 
                    new NodeGridDistanceFunction(
                        IntTupleDistanceFunctions.chebyshev());

aber das sieht man ja nach außen alles nicht.

Schon SEHR rethorisch. Willst du auf sowas wie Ein Name ist ein Name und kein String | open knowledge hinaus?

There are only two hard things in Computer Science: cache invalidation and naming things.

– Phil Karlton

Die Frage nach DistanceFunction oder ToDoubleBiFunction kommt letztem doch schon sehr nahe.

Ja, der Foliensatz kommt dem Ganzen schon sehr nahe, was ich meine der Kern der Frage zu sein scheint.

ToDoubleBiFunction ist ein primitiver Datentyp!
DistanceFunction nicht, das ist Domainmodel.

Die Frage ist, wo ziehst DU! die Grenze?
Vermeidest du weitgehende primitive Datentypen (im Sinne des Vortrags, nicht double vs. Double ;)) oder nutzt du diese und pfeifst auf ein ausdrucksstarkes Domainmodel?

Würdest du ein DateOfBirth zu einem LocalDate umwandeln?
Und wenn eine GUI-Komponente, Kalender auf LocalDate ausgerichtet ist?
Wie sähe es mit einem entsprechenden Converter aus?

Tendenziell würde, bzw. habe ich eher zur primitiven Variante gegriffen, allerdings bin ich doch am überlegen ob ein ausgewachsenes ausdrucksstarkes Domainmodel nicht die langfristig bessere Lösung ist.

Ich muss sagen, dass ich hier auch ein wenig biased (da fällt mir tatsächlich kein Deutsches Wort dazu ein) bin, durch das Programmieren in Sprachen wie JavaSciprt oder Clojure. Da sind Maps, anonyme Funktionen an der Tagesordnung. Input - Transformation - Output. Da braucht es nicht immer ein riesiges Domainmodel. Hat ungemeine Vorteile wenn sich was ändert oder man schnell, schnell was machen möchte.

Die Kernfrage ist -wenn ich es richtig verstanden habe- wann sollte man die bestehenden functional Interfaces erweitern? Vor der Frage stand ich auch schon öfter und habe sie mir so beantwortet: Immer dann, wenn es einen Mehrwert bietet.
Alleine, dass Du hier einen generischen Parameter sparst und die beiden Parameter auf einen gemeinsamen Typ festlegst, wäre für mich schon genug Mehrwert, um eine Erweiterung von ToDoubleBiFunction zu rechtfertigen. Über die Benennung der Erweiterung kann man trefflich streiten (möglichst generisch vs. sprechendes Domänenmodell). Da in diesem Fall (noch) nichts an Funktionalität, also Default-Methoden hinzu kommt, tendiere ich hier zu möglichst generisch. Was Du hier hast, ist ein ToDoubleBinaryOperator und so würde ich ihn nennen. Der hätte dann auch seine Daseinsberechtigung in einer etwas allgemeineren Lib.

ToDoubleBinaryOperator kann man einführen,
aber selbst mit dieser einfacherer Alternative für den Code bin ich immer noch dafür, eine DistanceFunction zu haben,

hängt freilich etwas davon ab, wie wichtig das für die Anwendung ist, wie oft verwendet,
wieviele Distanzfunktionen vorkommen, wie viele andere Funktionen (mit ähnlichen generischen Typ) zur Verwechslung vorhanden,

String + Date vs eigene Klassen Name, Birthdate geht in ähnliche Richtung, aber mit Funktionen ist es doch bisschen größer


ein nahes Vergleichsbeispiel ist der Comparator, liegt griffig in der Hand äh Variable,
nie wird dieser durch einen obskuren ToIntBinaryOperator entfremdet werden, man stelle sich das vor…,
wo allein mit der Doku hin? kann man gar nicht streichen,

aber wäre auch nicht wünschenswert, wie aussagelos sollen Typen und Parameter werden, ich halte nicht viel allein von Variablen- und Parameternamen + evtl. Doku,

wiederum dieselbe Frage gilt aber bei mehreren String-Parametern mit unterschiedlicher Aufgabe,
da wird man genau dazu gezwungen, auf die Variablennamen zu schauen,
aber String ist einfach farblos, hat hunderte Ausprägungen, da ist man dran gewöhnt, sonst allein dafür die Klassenzahl zu verzigfachen,

sehe ich dagegen nicht, Comparator & Co. haben ihren wichtigen Platz und es sind nicht zu viele,
bisher freilich die Standard-API vielleicht nur wenig auf Funktionen ausgerichtet, grundsätzlicher Kritikpunkt :wink:

Gewöhnungsfrage also, wie ionutbaiu schrieb, aber meiner Ansicht nach ist
new Person(String vorname, String nachname, String adresse)
auch für Programmierneulinge am ersten Tag an bereits ganz gut verständlich

während das für <R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
vielleicht nie gilt, ob Leser imperativ verzogen oder dem objektiv früh begegnet,

nicht nur Gewöhnungsfrage, bei Funktionen einfach grundsätzlich komplizierter

zu <R> R collect(Supplier<R> supplier, Accumulator<R,? super T> accumulator, Combiner<R> combiner)
wollte ich gerade schreiben dass das nicht unbedingt sofort helfen muss, notiert finde ich es aber auch schon gar nicht so schlecht,
allein dass der aussagelose Begriff ‚BiConsumer‘ wegfällt macht schon einiges aus

bei eigenen Klassen jedenfalls ist es noch weitaus wertvoller, Ärger wie ‚ToDoubleBiXy‘ fernzuhalten, mit lesbaren Begriffen umzugehen

Nun, die Vor- und Nachteile sind weitgehend klar. Im wesentlichen: 1. DistanceFunction ist ein schönerer Name mit schönerer Signatur, und 2. ToDoubleBiFunction ist allgemeiner und Teil der Standard-API. Es geht vielleicht („nur“?) um die Gewichtung…

Angnommen, ich habe eine Lib, die, wie oben schon angedeutet, z.B. ein „Datensatz“-Objekt anbietet:

interface DataSet<T> {
    DistanceFunction<T> getDistanceFunction();
    List<T> getData();
}

Diese Lib hat an sich die Abhängigkeit zu der Lib, wo DistanceFunction definiert ist. Dann könnte man sagen: Alles klar, die Abhängigkeit ist da, und das Interface mit dem schönen, sprechenden Namen ist da, als verwendet man es auch.

Jetzt gibt es aber eine weitere Lib. Die hat mit der DistanceFunction-Lib eigentlich nichts zu tun. Sie bietet nur eine Funktion an, die aus einer Liste von Objekten und einer Distanzfunktion eine Distanzmatrix berechnet:

double[][] computeDistanceMatrix(
     List<T> objects,
     DistanceFunction<? super T> distanceFunction) 
{
....
}

Es macht eigentlich keinen Sinn, dort die Abhängigkeit zur DistanceFunction-Lib einzubauen, weil die Funktion, die benötigt wird, besser als ToDoubleBiFunction gegeben sein sollte.

So gesehen bedeutet das aber, dass

  • DistanceFunction dann ToDoubleBiFunction erweitern müßte (nicht schlimm - so ist es im Moment) ODER
  • Jeder, der einen DataSet bekommt, die DistanceFunction selbst (per Lambda) als ToDoubleBiFunction an diese Funktion weiterreichen müßte:
computeDistanceMatrix(dataSet.getData(),
    (d, d) -> dataSet.getDistanceFunction().distance(d,d)); // = ToDoubleBiFunction

Dann stellt sich aber die Frage, ob DistanceFunction überhaupt noch eine Existenzberechtigung hat.

Natürlich könnte man die Frage verallgemeinern. In letzter Konsequenz wäre die Frage: „Sollte es mehrere strukturell gleiche Interfaces mit unterschiedlichen Namen geben?“. Ich weiß nicht, ob jemand auf die Idee kommen würde, aus jedem ActionListener nun einen Consumer<ActionEvent> zu machen - aber objektiv betrachtet wäre das nicht in jeder Hinsicht „schlecht“ … … …

Ein Argument für das DistanceFunction interface wäre, dass man es ggf. um Dinge erweitern könnte, die spezifisch für Distanzfunktionen sind…

interface DistanceFunction<T> {
    double distance(T t0, T t1);

    // For Euclidean distances, this is implemented 
    // differently, to avoid the expensive square root
    default double distanceSquared(T t0, T t1) {
        double d = distance(t0, t1);
        return d*d;
    }
}

womit die Lösung mit dem ToDoubleBinaryOperator auch wegfallen würde. (Auch da stellt sich die Frage: „Lohnt“ sich das, als eigene Klasse, die eine Abhängigkeit verursacht, wenn es doch schon mit einem existierenden Interface abgebildet werden kann…?)


EDIT @SlaterB Zeitliche Überschneidung, Update:

Das Beispiel mit dem Comparator hat ja gewisse Ähnlichkeit zu meinem mit dem ActionListener. Man könnte die Frage, wie schon angedeutet, seht weit verallgemeinern. Und schon in anderen Zusammenhängen hatte ich angedeutet, dass man, wenn man es darauf anlegt, 99% seiner Programme mit ein paar Set, Tuple und Function modellieren könnte (oder, um die LISP-Jünger zufrieden zu stellen (oder zu bashen) : 100% seiner Programme mit ein paar Listen :D).

Diese Generizität (an Funktionale Programmierung allgemein angelehnt) hat viele theoretische Vorteile, und auch praktische in bezug auf die Flexibilität. Trotzdem gibt es nunmal eine Sache, die Objektorienterte Programmierung ausmacht - die sie ist (oder „im innersten Zusammenhält“). Und das sind Namen. Sprechende Namen für die Dinge, die man da modelliert. Und diese Namen sind (dadurch, dass sie einem helfen, in Strukturen zu denken und damit auch komplexe Systeme hanhabbar zu halten) so gesehen der Erfolgsgrund der OOP.

Das ganze Problem würde schon dramatisch abgeschwächt, wenn es sowas wie typedef gäbe (aber nicht das aus C, sondern ein vernünftiges). Auf Java übertragen würde das bedeuten, dass der Compiler erkennen könnte (und sollte - ich frage mich, warum er das nicht tut… :frowning: ) dass zwei Interfaces eben strukturell gleich sind, bzw. das eine durch das andere abgebildet werden kann.

Sowas wie
<R> R collect(... Accumulator<R,? super T> accumulator, ...)
wäre ja durchaus nett und schön, wenn man es mit

BiConsumer<X,Y> myAccumulator = ...;
collect(... myAccumulator...);

aufrufen könnte - der Compiler könnte durchaus feststellen, dass er dafür „nur“ die trivial-Mechanische Umwandlung machen müßte, die ich oben schon für die DistanceFunction angedeutet hatte:

BiConsumer<X,Y> myAccumulator = ...;
Accumulator<X,Y> trivialAndInsertedByTheCompiler = (x,y) -> myAccumulator.accept(x,y);
collect(... trivialAndInsertedByTheCompiler ...);

(sicher, in anderen Fällen wäre das komplizierter, aber so spontan fragt man sich schon, warum er das nicht schaffen sollte…)


Die „typtheoretischen“ Überlegungen (und leichten Abschweifungen zu Compilerbau und Typinferenz) mal etwas außen vor gelassen: Ein wichtiger Punkt der Frage war die Abhängigkeit. Sollte man

interface DataSet<T> {
    DistanceFunction<T> getDistanceFunction();
    List<T> getData();
}

schreiben, und damit jeden, der das verwenden will, zur Verwendung der „lästigen“ (und so erstmal nicht wirklich notwendigen (und unmittelbar nicht mal nutzbringenden)) DistanceFunction-Lib nötigen, oder auf das allgemeine, Abhängigkeitsfreie

interface DataSet<T> {
    ToDoubleBiFunction<T, T> getDistanceFunction();
    List<T> getData();
}

zurückgreifen?

nicht ganz, von ActionListener wollte ich zunächst auch sprechen, aber dann doch epische Länge etwas vermieden :wink: :
zwischen ActionListener und Consumer für mich kaum Unterschied, wobei natürlich nett die <> einzusparen, auch Lambda-Abkürzung hier ok,
denn normalerweise verwendet man das nur direkt bei einem Button, evtl. Menü, ein Arbeitsinterface ohne größere Bedeutung,
das ActionEvent nutzt man gar nicht mal oft, eher für den Button intern interessant

falls man ein größeres eigenes fachliches Konzept hat mit verschiedenen ActionListenern (kaum vorstellbar),
dann wäre hier auch wieder meine Aussage, dass sich Unterklassen von dieser Grundklasse lohnen könnten,
verschiedene SaveAction, verschiedene SortAction, was immer anliegt


huch, nein, also für mich ein wichtiger Punkt der unterschiedlichen Interfaces, solche unklaren Aufrufe zu verhindern…,

zwar auch so noch einiges dran am konkreten Interfacen, diese Möglichkeit hat gewiss ihren Reiz,
aber, naja, sieht jeder anders

wenigstens ein aktiver Schritt wie Cast oder <> davor zur Sicherheit :wink:


Die „typtheoretischen“ Überlegungen (und leichten Abschweifungen zu Compilerbau und Typinferenz) mal etwas außen vor gelassen: Ein wichtiger Punkt der Frage war die Abhängigkeit. Sollte man

interface DataSet<T> {
    DistanceFunction<T> getDistanceFunction();
    List<T> getData();
}

schreiben, und damit jeden, der das verwenden will, zur Verwendung der „lästigen“ (und so erstmal nicht wirklich notwendigen (und unmittelbar nicht mal nutzbringenden)) DistanceFunction-Lib nötigen, oder […]

ist denn die DistanceFunction in noch einer anderen Library als DataSet, so dass man etwas von DataSet aufrufen kann ohne gleichzeitig schon DistanceFunction zu kennen?
scheint mir sehr weit gefasstes Problem, aber wenn so gedacht, dann sicherlich ein Punkt

ich bin für DistanceFunction, wer DataSet kennen will, soll es auch richtig kennen :wink:

klar kann DistanceFunction von ToDoubleBiFunction erben für allgemeine Weiterverarbeitung,
auch wenn zwei mögliche Aufruf-Methoden unschön sind

[QUOTE=SlaterB]nicht ganz, von ActionListener wollte ich zunächst auch sprechen, aber dann doch epische Länge etwas vermieden :wink: :

falls man ein größeres eigenes fachliches Konzept hat mit verschiedenen ActionListenern (kaum vorstellbar)
[/QUOTE]

Das ist (falls ich das richtig verstehe) tatsächlich eine der Kernursachen der Fragestellung: Welchen Typ kann man (oder muss man) dem „Ding“ geben, wenn man es einer Variable zuweisen will? (Bzw. per getter anbieten oder per setter setzen können will?)

Ja, teilweise ist das schon subjektiv. Aber die allgemeine Fragestellung ist IMHO durchaus gerechtfertigt:

IntegerToString           a = i -> String.valueOf(i);  // Geht
Function<Integer, String> b = i -> String.valueOf(i);  // Geht
a = b; // Geht nicht?! Warum nicht!? Das ist doch "das gleiche"!?

Auch wenn es im ersten Moment konfuzianistisch wirkt: Welchen Typ hat ein Lambda-Ausdruck, wenn er keiner Variablen zugewiesen wird? Die Language Designer werden sicher ihre Gründe gehabt haben, das der Target Type Inference zu überlassen, aber einen offensichtlichen, driftigen Grund, eine Zuweisung wie oben zu verbieten, sondern einem stattdessen ein
a = i -> b.apply(i);
abzunötigen, sehe ich jetzt nicht - vielleicht ist das aber auch nur der reinterpret_cast der Java-Welt :D.

Hmja, die genaue Verortung und Zusamenhänge jetzt zu erklären, wäre vielleicht nicht zielführend.

aber wen’s interessiert…

Ich bastle gerade (schon eine Weile) an einer Lib für „Mehrdimensionale Tupel und Arrays von Primitiven“, für int/double/long, mit allerhand Kleinram, ein bißchen funktionalem Zeux, (Spliteratoren für Tuples und Arrays), sowas wie tuple.subTuple(from, to) oder auch IntTupleIterators.colexicographicalIterator(min, max), und allem voran eben einem interface DoubleTuple.

Darauf aufbauend (im Moment noch komplett getrennt davon - bin noch nicht sicher, ob das so bleibt) gibt es eine eigene Lib, „NDDistance“, wo eben die ganzen Distanzfunktionen liegen. Bisher nur für DoubleTuple, mit herrlichen Klassennamen wie DoubleTupleDistanceFunctionEuclideanSquared oder DoubleTupleDistanceFunctionDynamicTimeWarping, aber Herr Chebyshev und Herr Mahalanobis werden sicher auch noch zum Zuge kommen. Dort liegt jedenfalls auch die DistanceFunction drin.

Konkret gibt es jetzt eine weitere Lib, die die ND und die NDDistance verwendet. Diese Lib hat z.B. eine Klasse Node, und muss mit Distanzfunktionen zwischen diesen hantieren. Es würde sich jetzt anbieten, dort DistanceFunction zu verwenden. Aber die stammt ja aus der „NDDistance“-Lib, die mit den Nodes so gesehen nichts zu tun hat. Deswegen würde ich da dann eher ToDoubleBiFunction<Node,Node> verwenden - „aus Prinzip“ (<- ein nicht-Argument, aber was damit gemeint ist, wird hoffentlich in diesem Thread deutlich).

(Der Extremfall wäre, dass man diese letzte Lib jetzt NICHT mehr auf ND/NDDistance aufbauen läßt - dann bräuchte man sie aber trotzdem noch, nur wegen dieser einen, lächerlichen DistanceFunction, die da irgendwo auftaucht, und die eigentlich auch eine ToDoubleBiFunction hätte sein können…)

[QUOTE=SlaterB;126125]
ich bin für DistanceFunction, wer DataSet kennen will, soll es auch richtig kennen :wink:

klar kann DistanceFunction von ToDoubleBiFunction erben für allgemeine Weiterverarbeitung,
auch wenn zwei mögliche Aufruf-Methoden unschön sind[/QUOTE]

Die beiden Aufruf-Methoden finde ich weniger kritisch. Tendenziell gehe ich im Moment in die Richtung…

  • DistanceFunction ist innerhalb der Lib selbst so ein „starkes“ (und ggf. noch zu erweiterndes!) Konzept, dass seine Existenz gerechtfertigt ist
  • Dort, wo die DistanceFunction nur „passiv verwendet“ werden soll, sollte man sie (entsprechend dem allgmeinen Prinzip) nur als die „unspezifischste Klasse“ kennen und weiterreichen, die nötig ist - und an vielen Stellen wäre das eben ToDoubleBiFunction. Es gibt keinen Grund, in einer Methodensignatur eine DistanceFunction zu erwarten, wenn dort auch eine ToDoubleBiFunction reicht (genau wie man nicht List verwendet, wenn Collection reicht)
  • Dort, wo es um Distanzfunktionen geht, die nichts mit der Kern-Lib (wo dieses Interface definiert ist) zu tun haben, wird immer eine ToDoubleBiFunction verwendet (um die Abhängigkeit „abzuschwächen“ - auch wenn sie Aufgrund anderer Verbindungen faktisch immer da sein wird)