Warum ist Math.Sqrt () eine statische Funktion?

31

Bei einer Diskussion über statische und Instanzmethoden denke ich immer, dass Sqrt()dies eine Instanzmethode mit Zahlentypen anstelle einer statischen Methode sein sollte. Warum das? Es funktioniert offensichtlich auf einem Wert.

 // looks wrong to me
 var y = Math.Sqrt(x);
 // looks better to me
 var y = x.Sqrt();

Werttypen können natürlich Instanzmethoden haben, da es in vielen Sprachen eine Instanzmethode gibt ToString().

Um einige Fragen aus den Kommentaren zu beantworten: Warum sollte 1.Sqrt()nicht legal sein? 1.ToString()ist.

In einigen Sprachen sind Methoden für Werttypen nicht zulässig, in einigen Sprachen jedoch. Ich spreche über diese, einschließlich Java, ECMAScript, C # und Python (mit __str__(self)definierten). Gleiches gilt für andere Funktionen wie ceil(), floor()etc.

Residuum
quelle
18
In welcher Sprache schlagen Sie das vor? Wäre 1.sqrt()gültig?
20
In vielen Sprachen (z. B. Java) sind Doubles (aus Performancegründen) Primative, sodass sie keine Methoden haben
Richard Tingle
45
So sollten numerische Typen mit jeder möglichen mathematischen Funktion aufgebläht werden, die auf sie angewendet werden könnte?
D Stanley
19
FWIW Ich denke, es Sqrt(x)sieht viel natürlicher aus als x.Sqrt() Wenn das bedeutet, dass der Klasse in einigen Sprachen die Funktion vorangestellt wird, bin ich damit einverstanden. Wenn es gab eine Instanzmethode dann x.GetSqrt()wäre besser geeignet sein , um anzuzeigen , dass es Rückkehr Wert anstatt Modifizieren der Instanz.
D Stanley
23
Diese Frage kann in ihrer jetzigen Form nicht sprachunabhängig sein. Das ist die Wurzel des Problems.
risingDarkness

Antworten:

20

Es ist völlig eine Wahl der Sprachgestaltung. Dies hängt auch von der zugrunde liegenden Implementierung primitiver Typen und den damit verbundenen Leistungsaspekten ab.

.NET hat nur eine statische Math.SqrtMethode, die auf a einwirkt doubleund a zurückgibtdouble . Alles andere, was du ihm übergibst, muss besetzt oder zu a befördert werden double.

double sqrt2 = Math.Sqrt(2d);

Auf der anderen Seite haben Sie Rust, das diese Operationen als Funktionen für die folgenden Typen verfügbar macht :

let sqrt2 = 2.0f32.sqrt();
let higher = 2.0f32.max(3.0f32);

Rust verfügt jedoch auch über eine universelle Funktionsaufrufsyntax (wie bereits erwähnt), sodass Sie auswählen können, was Sie möchten.

let sqrt2 = f32::sqrt(2.0f32);
let higher = f32::max(2.0f32, 3.0f32);
engelchen
quelle
1
Es ist erwähnenswert, dass Sie in .NET Erweiterungsmethoden schreiben können. Wenn Sie also wirklich möchten, dass diese Implementierung so aussieht x.Sqrt(), können Sie dies tun. public static class DoubleExtensions { public static double Sqrt( this double self) { return Math.Sqrt(self); } }
Zachary Dow
1
Auch in C # 6 kann es nur Sqrt(x) msdn.microsoft.com/en-us/library/sf0df423.aspx sein .
Den
65

Angenommen, wir entwerfen eine neue Sprache und möchten Sqrteine Instanzmethode sein. Also schauen wir uns die doubleKlasse an und beginnen mit dem Entwerfen. Es hat offensichtlich keine Eingaben (außer der Instanz) und gibt a zurück double. Wir schreiben und testen den Code. Perfektion.

Aber die Quadratwurzel einer ganzen Zahl zu ziehen ist auch gültig, und wir wollen nicht jeden zwingen, in ein Double zu konvertieren, nur um eine Quadratwurzel zu ziehen. Also ziehen wir um intund beginnen mit dem Entwerfen. Was kommt zurück? Wir könnten ein zurückgeben intund es nur für perfekte Quadrate arbeiten lassen oder das Ergebnis auf das nächste runden int(ohne die Debatte über die richtige Rundungsmethode im Moment). Aber was ist, wenn jemand ein nicht ganzzahliges Ergebnis haben möchte? Sollten wir zwei Methoden haben - eine, die eine zurückgibt, intund eine, die eine zurückgibt double(was in einigen Sprachen nicht möglich ist, ohne den Namen zu ändern)? Also entscheiden wir, dass es a zurückgeben soll double. Jetzt setzen wir um. Die Implementierung ist jedoch identisch mit der, für die wir verwendet habendouble. Kopieren und Einfügen? Umwandeln wir die Instanz in eine doubleund rufen diese Instanzmethode auf? Stellen Sie die Logik in eine Bibliotheksmethode, auf die von beiden Klassen aus zugegriffen werden kann. Wir rufen die Bibliothek Mathund die Funktion auf Math.Sqrt.

Warum ist Math.Sqrteine statische Funktion ?:

  • Weil die Implementierung unabhängig vom zugrunde liegenden numerischen Typ identisch ist
  • Da es sich nicht auf eine bestimmte Instanz auswirkt (es nimmt einen Wert auf und gibt ein Ergebnis zurück)
  • Da numerische Typen nicht von dieser Funktionalität abhängen , ist es sinnvoll, sie in einer separaten Klasse zu haben

Wir haben noch nicht einmal andere Argumente angesprochen:

  • Sollte es benannt werden, GetSqrtda es einen neuen Wert zurückgibt , anstatt die Instanz zu ändern?
  • Was ist Square? Abs? Trunc? Log10? Ln? Power? Factorial? Sin? Cos? ArcTan?
D Stanley
quelle
6
Ganz zu schweigen von den Freuden von 1.sqrt()vs 1.1.sqrt()(Ghads, das sieht hässlich aus), haben sie eine gemeinsame Basisklasse? Was ist der Vertrag für seine sqrt()Methode?
5
@MichaelT Schönes Beispiel. Ich brauchte vier Lesungen, um zu verstehen, was 1.1.Sqrtdargestellt wurde. Klug.
D Stanley
17
Ich bin nicht wirklich klar aus dieser Antwort, wie die statische Klasse mit Ihrem Hauptgrund hilft. Wenn Sie in Ihrer Math-Klasse double Sqrt (int) und double Sqrt (double) haben, haben Sie zwei Möglichkeiten: Konvertieren Sie das int in ein double, rufen Sie dann die double-Version auf, oder kopieren Sie die Methode und fügen Sie sie mit den entsprechenden Änderungen ein (falls vorhanden) ). Dies sind jedoch genau die Optionen, die Sie für die Instanzversion beschrieben haben. Ihre andere Argumentation (insbesondere Ihr dritter Aufzählungspunkt) stimme ich mehr zu.
Ben Aaronson
14
-1 diese antwort ist absurd, was hat das alles damit zu tun, statisch zu sein? Sie werden entscheiden müssen, ob Sie die gleichen Fragen beantworten wollen oder nicht (und "[mit einer statischen Funktion] ist die Implementierung die gleiche" ist falsch oder zumindest nicht wahrer als es zum Beispiel Methoden wären.)
BlueRaja - Danny Pflughoeft
20
"Die Implementierung ist unabhängig vom zugrunde liegenden numerischen Typ dieselbe" ist völliger Unsinn. Die Implementierungen der Quadratwurzelfunktion müssen sich je nach Typ, mit dem sie arbeiten, erheblich unterscheiden, um nicht fürchterlich ineffizient zu sein.
R ..
25

Mathematische Operationen sind oft sehr leistungsabhängig. Aus diesem Grund möchten wir statische Methoden verwenden, die zum Zeitpunkt der Kompilierung vollständig aufgelöst (und optimiert oder inline) werden können. Einige Sprachen bieten keinen Mechanismus zur Angabe statisch versendeter Methoden. Darüber hinaus hat das Objektmodell vieler Sprachen einen erheblichen Speicheraufwand, der für "primitive" Typen wie z double.

In einigen Sprachen können Funktionen definiert werden, die Methodenaufrufsyntax verwenden, aber tatsächlich statisch gesendet werden. Ein Beispiel sind Erweiterungsmethoden in C # 3.0 oder höher. Nicht-virtuelle Methoden (z. B. die Standardeinstellung für Methoden in C ++) sind ein weiterer Fall, obwohl C ++ keine Methoden für primitive Typen unterstützt. Selbstverständlich können Sie in C ++ eine eigene Wrapper-Klasse erstellen, die einen primitiven Typ mit verschiedenen Methoden verziert, ohne dass dies zu einem Laufzeitaufwand führt. Sie müssen die Werte jedoch manuell in diesen Wrapper-Typ konvertieren.

Es gibt einige Sprachen, die Methoden für ihre numerischen Typen definieren. Dies sind normalerweise hochdynamische Sprachen, in denen alles ein Objekt ist. Hier spielt die Leistung eine untergeordnete Rolle für die konzeptionelle Eleganz, aber diese Sprachen werden im Allgemeinen nicht für die Zahlenverarbeitung verwendet. Diese Sprachen verfügen jedoch möglicherweise über ein Optimierungsprogramm, mit dem Vorgänge auf Grundelementen „entpackt“ werden können.


Mit den technischen Überlegungen können wir überlegen, ob eine solche methodenbasierte mathematische Schnittstelle eine gute Schnittstelle wäre. Es treten zwei Probleme auf:

  • Die mathematische Notation basiert auf Operatoren und Funktionen, nicht auf Methoden. Ein Ausdruck wie 42.sqrterscheint vielen Benutzern viel fremder als sqrt(42). Als mathematikintensiver Benutzer würde ich es vorziehen, meine eigenen Operatoren anstelle der Punktmethodenaufrufsyntax zu erstellen.
  • Das Prinzip der einheitlichen Verantwortung ermutigt uns, die Anzahl der Vorgänge, die Teil eines Typs sind, auf die wesentlichen Vorgänge zu beschränken. Verglichen mit der Multiplikation benötigen Sie die Quadratwurzel bemerkenswert selten. Wenn Ihre Sprache , die speziell für die statistische anlysis bestimmt ist, dann mehr Primitiven bietet (wie Operationen mean, median, variance, std, normalizeauf numerischen Listen oder die Gamma - Funktion für Zahlen) kann nützlich sein. Für eine Allzwecksprache belastet dies lediglich die Benutzeroberfläche. Durch das Verknüpfen nicht notwendiger Vorgänge in einen separaten Namespace wird der Zugriff auf den Typ für die Mehrheit der Benutzer erleichtert.
amon
quelle
Python ist ein gutes Beispiel für eine Alles-ist-ein-Objekt-Sprache, die für das Knacken von Zahlen verwendet wird. NumPy-Skalare haben tatsächlich Dutzende von Methoden, aber sqrt ist immer noch keine davon. Die meisten von ihnen sind Dinge wie transposeund meandass gibt es nur eine einheitliche Schnittstelle mit NumPy Arrays zu schaffen, das die realen Arbeitspferd Datenstruktur ist.
user2357112 unterstützt Monica
7
@ user2357112: Die Sache ist, dass NumPy selbst in einer Mischung aus C und Cython mit etwas Python-Kleber geschrieben ist. Sonst könnte es nie so schnell sein wie es ist.
Kevin
1
Ich denke, diese Antwort kommt einer Art realem Kompromiss sehr nahe, der im Laufe der Jahre des Designs erzielt wurde. In anderen Nachrichten ist es in .Net wirklich sinnvoll, "Hello World" .Max () zu können, da die LINQ-Erweiterungen es uns ermöglichen UND in Intellisense sehr gut sichtbar machen. Bonuspunkte: Was ist das Ergebnis? Bonus Bonus, was ist das Ergebnis in Unicode ...?
Andyz Smith
15

Ich würde durch die Tatsache motiviert sein, dass es eine Menge spezieller mathematischer Funktionen gibt, und anstatt jeden mathematischen Typ mit allen (oder einer zufälligen Teilmenge) dieser Funktionen zu füllen, ordnen Sie sie einer Hilfsklasse zu. Andernfalls würden Sie entweder Ihren Tooltip zur automatischen Vervollständigung verschmutzen oder die Leute zwingen, immer an zwei Stellen nachzuschauen. (Ist sinwichtig genug, um Mitglied zu sein Double, oder ist es in der MathKlasse zusammen mit Inzuchten wie htanund exp1p?)

Ein weiterer praktischer Grund ist, dass es möglicherweise verschiedene Möglichkeiten gibt, numerische Methoden mit unterschiedlichen Kompromissen in Bezug auf Leistung und Präzision zu implementieren. Java hat Mathund es hat auch StrictMath.

Aleksandr Dubinsky
quelle
Ich hoffe, Sprachdesigner interessieren sich nicht für Tooltips mit automatischer Vervollständigung. Wie geht es weiter Math.<^space>? Dieser Auto-Vervollständigungs-Tooltip wird ebenfalls verschmutzt. Umgekehrt denke ich, dass Ihr zweiter Absatz wahrscheinlich eine der besseren Antworten hier ist.
Qix
@ Qix Sie tun. Andere Leute hier nennen es vielleicht "Aufblähen einer Benutzeroberfläche".
Aleksandr Dubinsky
6

Sie haben richtig bemerkt, dass hier eine merkwürdige Symmetrie vorliegt.

Ob ich sage sqrt(n)oder n.sqrt()nicht, beide drücken dasselbe aus, und welches Sie bevorzugen, ist mehr eine Frage des persönlichen Geschmacks als alles andere.

Das ist auch der Grund, warum bestimmte Sprachdesigner ein starkes Argument dafür haben, die beiden Syntaxen austauschbar zu machen. Die Programmiersprache D ermöglicht dies bereits mit einer Funktion namens Uniform Function Call Syntax . Eine ähnliche Funktion wurde auch für die Standardisierung in C ++ vorgeschlagen . Wie Mark Amery in den Kommentaren betont, erlaubt Python dies auch.

Das ist nicht ohne Probleme. Die Einführung einer solchen grundlegenden Syntaxänderung hat weitreichende Konsequenzen für den vorhandenen Code und ist natürlich auch ein Thema kontroverser Diskussionen unter Entwicklern, die seit Jahrzehnten darauf trainiert sind, die beiden Syntaxen als Beschreibung verschiedener Dinge zu betrachten.

Ich denke, nur die Zeit wird zeigen, ob die Vereinigung der beiden auf lange Sicht machbar ist, aber es ist definitiv eine interessante Überlegung.

ComicSansMS
quelle
Python unterstützt diese beiden Syntaxen bereits. Jede nicht statische Methode verwendet selfals ersten Parameter und wenn Sie die Methode als Eigenschaft einer Instanz anstatt als Eigenschaft der Klasse aufrufen, wird die Instanz implizit als erstes Argument übergeben. Daher kann ich schreiben "foo".startswith("f")oder str.startswith("foo", "f")und ich kann schreiben my_list.append(x)oder list.append(my_list, x).
Mark Amery
@ MarkAmery Guter Punkt. Dies ist nicht ganz so drastisch wie das, was D oder die C ++ - Vorschläge tun, aber es passt zur allgemeinen Idee. Danke für den Hinweis!
ComicSansMS
3

Neben der Antwort von D Stanley muss man sich auch mit Polymorphismus auseinandersetzen. Methoden wie Math.Sqrt sollten immer denselben Wert an dieselbe Eingabe zurückgeben. Das Festlegen einer statischen Methode ist eine gute Möglichkeit, um diesen Punkt zu verdeutlichen, da statische Methoden nicht überschrieben werden können.

Sie haben die ToString () - Methode erwähnt. Hier möchten Sie möglicherweise diese Methode überschreiben, damit die (Unter-) Klasse auf andere Weise als String als übergeordnete Klasse dargestellt wird. Sie machen es also zu einer Instanzmethode.

falx
quelle
2

Nun, in Java gibt es einen Wrapper für jeden Basistyp.
Und Basistypen sind keine Klassentypen und haben keine Elementfunktionen.

Sie haben also die folgenden Möglichkeiten:

  1. Sammeln Sie alle diese Hilfsfunktionen in einer Pro-Forma-Klasse wie Math.
  2. Machen Sie es eine statische Funktion auf dem entsprechenden Wrapper.
  3. Machen Sie es zu einer Member-Funktion auf dem entsprechenden Wrapper.
  4. Ändern Sie die Regeln von Java.

Schließen wir Option 4 aus, weil ... Java Java ist und Anhänger es so mögen.

Jetzt können wir ausschließen auch Option 3 , da während Objekte Zuteilung ziemlich billig ist, ist es nicht frei ist, und tun , dass immer und immer wieder nicht aufaddieren.

Zwei Fehler, einer noch zu töten: Option 2 ist ebenfalls eine schlechte Idee, da dies bedeutet, dass jede Funktion für jeden Typ implementiert werden muss , man sich nicht darauf verlassen kann, die Konvertierung zu erweitern, um die Lücken zu füllen , da sonst die Inkonsistenzen wirklich schaden.
Und wenn man sich das ansieht java.lang.Math, gibt es viele Lücken, insbesondere für Typen, die kleiner als die intjeweiligen sind double.

Am Ende ist der klare Sieger also die erste Option, die alle an einem Ort in einer Utility-Funktionsklasse sammelt.

Zurück zu Option 4, etwas in dieser Richtung passierte tatsächlich viel später: Sie können den Compiler auffordern, alle statischen Member einer beliebigen Klasse zu berücksichtigen, wenn Sie Namen seit geraumer Zeit auflösen. import static someclass.*;

Abgesehen davon haben andere Sprachen dieses Problem nicht, entweder weil sie keine Vorurteile gegen freie Funktionen (optional unter Verwendung von Namespaces) haben oder weit weniger kleine Typen.

Deduplizierer
quelle
1
Berücksichtigen Sie die Freude an der Implementierung der Variationen Math.min()in allen Wrapper-Typen.
Ich finde # 4 nicht überzeugend. Math.sqrt()wurde zur gleichen Zeit wie der Rest von Java erstellt. Als also die Entscheidung getroffen wurde, sqrt () einzufügen, Mathgab es keine historische Trägheit für Java-Benutzer, die es "so mögen". Während es mit nicht viel von einem Problem gibt sqrt(), ist das Überlastungsverhalten von Math.round()schrecklich. Die Möglichkeit , Mitglied Syntax mit Werten des Typs zu verwenden floatund doublewürde dieses Problem vermieden werden kann.
Supercat
2

Ein Punkt, den ich nicht explizit erwähne (obwohl amon darauf anspielt), ist, dass die Quadratwurzel als "abgeleitete" Operation gedacht werden kann: Wenn die Implementierung dies nicht für uns bereitstellt, können wir unsere eigene schreiben.

Da die Frage mit Sprachdesign markiert ist, können wir eine sprachunabhängige Beschreibung in Betracht ziehen. Obwohl viele Sprachen unterschiedliche Philosophien haben, ist es in allen Paradigmen sehr verbreitet, die Kapselung zu verwenden, um Invarianten zu bewahren. dh zu vermeiden, einen Wert zu haben, der sich nicht so verhält, wie es der Typ vermuten lässt.

Wenn wir zum Beispiel eine Implementierung von Ganzzahlen mit Maschinenwörtern haben, möchten wir wahrscheinlich die Darstellung irgendwie kapseln (z. B. um zu verhindern, dass Bitverschiebungen das Vorzeichen ändern), aber gleichzeitig benötigen wir immer noch Zugriff auf diese Bits, um Operationen wie zu implementieren Zusatz.

Einige Sprachen können dies mit Klassen und privaten Methoden implementieren:

class Int {
    public Int add(Int x) {
      // Do something with the bits
    }
    private List<Boolean> getBits() {
      // ...
    }
}

Einige mit Modulsystemen:

signature INT = sig
  type int
  val add : int -> int -> int
end

structure Word : INT = struct
  datatype int  = (* ... *)
  fun add x y   = (* Do something with the bits *)
  fun getBits x = (* ... *)
end

Einige mit lexikalischem Geltungsbereich:

(defun getAdder ()
   (let ((getBits (lambda (x) ; ...
         (add     (lambda (x y) ; Do something with the bits
     'add))

Und so weiter. Doch keines dieser Mechanismen sind für die Umsetzung Quadratwurzel benötigt: sie umgesetzt werden kann , die mit der öffentlichen Schnittstelle eines numerischen Typ, und daher macht es keinen Zugriff auf die verkapselte Implementierungsdetails müssen.

Daher hängt die Position der Quadratwurzel von der Philosophie / dem Geschmack der Sprache und des Bibliotheksdesigners ab. Einige können es als "innerhalb" der numerischen Werte festlegen (z. B. als Instanzmethode festlegen), andere können es auf der gleichen Ebene wie die primitiven Operationen festlegen (dies kann eine Instanzmethode bedeuten, oder es kann bedeuten, dass Sie außerhalb der numerische Werte, aber innerhalb desselben Moduls / derselben Klasse / desselben Namespaces (z. B. als eigenständige Funktion oder statische Methode), können einige diese in eine Sammlung von "Hilfsfunktionen" einfügen, andere können sie an Bibliotheken von Drittanbietern delegieren.

Warbo
quelle
Nichts würde eine Sprache daran hindern, das Aufrufen einer statischen Methode entweder mithilfe der Member-Syntax (wie bei C # - oder vb.net-Erweiterungsmethoden) oder mit einer Variation der Member-Sequenz zuzulassen (ich hätte also gerne eine Doppelpunkt-Syntax gesehen) um die Intellisense-Vorteile zu ermöglichen, nur Funktionen aufzulisten, die für das primäre Argument geeignet waren, aber Unklarheiten mit realen Mitgliedsoperatoren zu vermeiden).
Supercat
-2

In Java und C # ist ToString eine Objektmethode, der Stamm der Klassenhierarchie, sodass jedes Objekt die ToString-Methode implementiert. Für einen Integertyp ist es nur natürlich, dass die Implementierung von ToString auf diese Weise funktioniert.

Sie argumentieren also falsch. Der Grund, warum Werttypen ToString implementieren, ist nicht, dass manche Leute es so fanden: Hey, lassen Sie uns eine ToString-Methode für Werttypen haben. Das liegt daran, dass ToString bereits vorhanden ist und dass die Ausgabe "am natürlichsten" ist.

Pieter B
quelle
1
Natürlich ist es eine Entscheidung, dh die Entscheidung, objecteine ToString()Methode zu haben . Das ist in Ihren Worten "einige Leute waren wie: Hey, lassen Sie uns eine ToString-Methode für Werttypen haben".
Residuum
-3

Im Gegensatz zu String.substring ist Number.sqrt nicht wirklich ein Attribut der Zahl, sondern ein neues Ergebnis, das auf Ihrer Zahl basiert. Ich denke, dass die Übergabe Ihrer Nummer an eine Quadrierungsfunktion intuitiver ist.

Darüber hinaus enthält das Math-Objekt andere statische Elemente, und es ist sinnvoller, sie zusammenzufassen und einheitlich zu verwenden.

HaLeiVi
quelle
3
Zähler Beispiel: BigInteger.pow () .