2016-07-21 19 views
0

Ich mache einige grundlegende Module über 2D-Geometrie und ich habe beschlossen, Koordinaten in newtypes zu wickeln. Dies ist so, dass ich leicht kartesische Koordinaten (newtype Xy = Xy(Float,Float)) von Polarkoordinaten unterscheiden kann (newtype Ra=Ra(Float,Float)), vielleicht auch komplexe Zahlen; auch um sie Instanzen von Num und Fractional zu machen, damit ich Betreiber überlasten kann.Fractional Koordinaten newtype

Aber müssen sie entweder als ein Paar Float s oder als Paar Double s definiert werden? In einigen trigonometrischen Funktionen könnte Double s merklich besser in Bezug auf Präzision arbeiten; viele der Funktionen, die ich für den Xy newtype schreibe Ich möchte, dass sie so allgemein wie möglich sind. Also gibt es eine Möglichkeit, einen neuen Typ mit einem Paar Nummern der Fractional Klasse zu machen?

Auch, weil ich es ein wenig umständlich zu finden, zu schreiben Xy (0,0) anstatt einfach (0,0), ich machte die · Betreiber:

(·) a b = Xy (a,b) 

Aber es scheint, Vorrang vor dem Rest zu haben, so 3+4·2+1 als 3+(Xy (4,2))+1 bewertet . Und es hilft auch nicht mit Funktionsdeklarationen, Lambda-Ausdrücke, etc ... Ich muss immer noch \Xy (a,b)-> schreiben.

Danke.

+0

Wie für die '·' Operator (die wirklich in einer separaten Frage gehört) - Sie benötigen eine _Fixement-Deklaration_, z. 'infixl 7 ·' (das ist der äquivalente '^ &' Operator in [der 'diagrams' Bibliothek] (http://hackage.haskell.org/package/diagrams-lib-1.3.1.3/docs/Diagrams-Coordinates. html # v: -94-38-) verwendet). – leftaroundabout

Antwort

4

Sie können einen Typparameter in Ihrem newtype haben:

newtype Xy n = Xy (n, n) 

Dann können Sie Funktionen schreiben, die in der Fractional Klasse auf Paare etwas arbeiten:

foo :: Fractional n => Xy n -> Xy n -> ... 

Sie auch bestimmte Arten verwenden können wie , wenn Sie Operationen haben, die nur für Doppel sinnvoll sind.

Leider kann man nicht nur die Tupel Syntax wiederverwenden ((a, b)), aber es ist überschaubar, wenn Sie eine Art schreiben, die zwei Felder hat stattdessen ein Paar Verpackung:

data Xy n = Xy n n 

Jetzt können Sie einfach schreiben Xy a b als ein Wert oder ein Muster. Sie können sogar eine Platte machen, um bequem Feld Syntax zu erhalten:

data Xy n = Xy { x, y :: n } 

Dies funktioniert genauso wie die vorherige Version, aber auch können Sie x und y als Funktionen (dh x :: Xy n -> n) und in Mustern verwenden.

+0

Danke Tichon. Datentypen sind sicherlich sauberer aussehen (plus andere Vorteile), aber es ist mein Verständnis, dass sie Leistung ein wenig wegen der Verpackung und Entpackung verlangsamen, anders als neue Typen. Koordinaten sind etwas Grundlegendes, das in großen Zahlen auftritt, wenn man Dinge wie glatte Kurven definiert, also beunruhigt mich im Voraus, dass ich neue Typen gewählt habe. –

+1

@ AsíMaullJosettra Ein 'newtype' wird auf die gleiche Weise dargestellt _ wie der Wrapper. Also 'newtype Xy n = Xy (n, n)' erscheint zur Laufzeit wie '(n, n)' 'Hier ist der wichtige Teil: **' (n, n) 'ist selbst ein Datentyp **. Tatsächlich ist die In-Memory-Repräsentation von "(n, n)" die gleiche wie die Repräsentation von "Daten Xy n = Xy n n" (ein Zeiger auf einen Thunk, der ein Paar Zeiger enthält). Ihre 'newtype' Version ist also genau so wie @ TikhonJelvis' data' Vorschlag. Die einzige Zeit, die der 'newtype' kostet, ist weniger, wenn Sie zu oder von einem Tupel konvertieren. –

+0

Oh, das ändert die Dinge! Ich werde dann Daten verwenden, da es auch einige andere Vorteile hat. Danke, dass du das erklärst! –

2

IMO sollten Sie wahrscheinlich nur Double verwenden, immer und hart gebacken. Auf einem x86-64-Prozessor gibt es keinen wirklichen Vorteil, den Float Ihnen geben würde - es benötigt den gleichen Speicher (da jeder Wert einen 64-Bit-Zeiger benötigt, um darauf zu verweisen), ist nicht schneller (könnte sogar zusätzlichen Ausrichtungsaufwand erfordern) - Es hat keinen Sinn. Double ist in Ordnung, es ist schnell und die beste Präzision, die Sie normalerweise anstreben können .

Es ist sicherlich viel besser als wenn Sie, OO-like, eine generische Instanz der Bruchklasse gespeichert - diese Art von Polymorphismus verursacht eine recht schwere Überlastung.Zum Glück scheut Haskell auch wirklich storing polymorphic values.

I konzeptionell sinnvoll, es nicht auch prüfen Xy als Funktors, das heißt eine Art zu definieren, dass parametrisch polymorphe über den Typen-Koordinate, wie durch Tikhon Jelvis vorgeschlagen. Während dies absolut ein gültiger Ansatz ist und wirklich gute Leistung geben kann (so funktioniert das linear library), vermisst dies, worum es bei Vektoren geht. Ein mathematischer Vektor ist in erster Linie nicht ein Tupel von Zahlen, aber a quantity that has both a direction and a magnitude. Die tatsächlichen Koordinaten eines solchen Vektors sollte nur ein Implementierungsdetail betrachtet werden.

Sie möchten auch nicht diese 2D-Dinge-Instanzen der Klassen Num usw. machen. Diese Klassen sind für numbers - Mengen, die kanonisch hinzugefügt werden können, subtrahiert und multipliziert. Sie können Vektoren jedoch nicht multiplizieren, zumindest nicht in einer Weise, die Ihnen wieder einen Vektor geben sollte. Die korrekte Klasse für Vektoren ist VectorSpace, nicht Num.


Es sollte angemerkt werden, dass packedFloat Arrays viel Speicher zu tun speichern, und wenn Sie vectorised processor instructions moderne verwenden können sie auch effektiv viel schneller sein. Aber dies sind Optimierungen, die auf der Ebene, auf der Sie über einzelne Tupel sprechen, nicht wirklich zugänglich sind.