2010-02-06 7 views
9

ich eine curried Funktion haben, die Ich mag würde es verschiedene Arten von Parametern zu unterstützen, die nicht auf einer Vererbungsbeziehung sind:F # Mustervergleich auf Arten von Tupeln

type MyType1 = A | B of float 
type MyType2 = C | D of int 

Was ich versucht zu tun ist:

let func x y = 
    match (x, y) with 
    | :? Tuple<MyType1, MyType1> -> "1, 1" 
    | _ -> "..." 

Dies ist jedoch nicht möglich. F # beschwert sich:

Der Typ '' a * 'b' hat keine geeigneten Subtypen und kann nicht als Quelle für einen Typprüfung oder eine Laufzeitnotation verwendet werden.

Was ist eine elegante Möglichkeit, dies zu tun?

EDIT: Lassen Sie mich versuchen, dies zu klären.

Ich habe zwei ähnliche, aber unterschiedliche Typen. Ich kann sehr einfach einen Typ in einen anderen konvertieren. Ich möchte eine binäre Operation definieren, die auf Entitäten dieser Typen wirkt, aber ich möchte dem Client eine einzelne Operation offen legen.

Das heißt, anstelle der Bereitstellung:

let op11 (x : MyType1) (y : MyType1) = // do something useful 
let op12 (x : MyType1) (y : MyType2) = 
    // convert y to MyType1 
    let y' = // ... 
    // dispatch to op11 
    op11 x y' 
let op21 (x : MyType2) (y : MyType1) = // similar 
let op22 (x : MyType2) (y : MyType2) = // similar 

was würde Ich mag es, eine einzige Funktion zu Client-Code zu entlarven:

let op (x : obj) (y : obj) = // ... 

Das ist wie das Verhalten der Methode Überlastung zu simulieren, aber mit Curry-Funktionen.

+0

Typprüfung in F #? Da stimmt etwas nicht. Was * genau * versuchst du zu tun? – Juliet

+0

Ich verstehe nicht, was Sie fragen - was ist die Signatur von 'Func' (was sind die Arten von 'x' und 'y')? – Brian

Antwort

14

Ihr Code funktioniert nicht, weil F # den Typ der Argumente für einen Typparameter verallgemeinert. Ich denke, man kann nicht dynamisch testen, ob ein Typ 'a * 'b in den Typ MyType1 * MyType2 konvertiert werden kann (obwohl das für mich etwas verwirrend ist). In jedem Fall können Sie eine Funktion schreiben, die zwei Argumente vom Typ nimmt obj und testet sie separat mit zwei :? Muster:

type MyType1 = A | B of float 
type MyType2 = C | D of int 

let func (x:obj) (y:obj) = 
    match (x, y) with 
    | (:? MyType1 as x1), (:? MyType1 as x2) -> 
     printfn "%A %A" x1 x2 
    | _ -> 
     printfn "something else" 

func A (B 3.0) // A B 3.0 
func A (D 42) // something else 

Wie dem auch sei, wäre es interessant zu wissen, warum wollen Sie das tun? Es kann eine bessere Lösung ...

EDIT (2) also von all 4 aus zwei Elementen bestehenden Kombinationen von T1 und T2, können Sie die Funktion mögen, die 3. Ist das richtig nehmen (T1 * T1, T1 * T2 und T2 * T2)? In diesem Fall können Sie keine vollständig sichere Curry-Funktion schreiben, da der Typ des zweiten Arguments vom Typ des ersten Arguments abhängt (wenn das erste Argument den Typ T2 hat, muss das zweite Argument auch T2 sein (sonst kann es auch T1 sein)).

Sie können eine sichere Nicht-curried Funktion schreiben, die ein Argument des Typs nimmt:

type MyArg = Comb1 of T1 * T1 | Comb2 of T1 * T2 | Comb3 of T2 * T2 

Die Art der Funktion MyArg -> string wäre. Wenn Sie eine Curry-Funktion wünschen, können Sie einen Typ definieren, der entweder T1 oder T2 als erstes und zweites Argument verwendet.

type MyArg = First of T1 | Second of T2 

Dann wird Ihr curried Funktion MyArg -> MyArg -> string sein. Beachten Sie jedoch, dass eine Kombination von Argumenttypen nicht zulässig ist (wenn ich Sie richtig verstehe, sollte T2 * T1 nicht zulässig sein). In diesem Fall muss Ihre Funktion einfach eine Ausnahme oder ähnliches auslösen.

+1

Ausgezeichnet! Das ist, was ich suchte, eine Möglichkeit, zwei ':?' Muster zu verwenden. Vielen Dank. –

+0

Warum: viel vereinfacht, ich habe das: 'type data = int',' type MyType = A | B der Daten | C von Daten * Daten'. Ich möchte eine 'makeC: ??? -> ??? -> MyType', das entweder 2 'data's oder ein' data' und ein 'B (x)' oder zwei 'B (x)' s benötigt. –

+0

In Bezug auf 'makeC' wollen Sie diese Funktion nicht wirklich. – Brian

1

Das riecht sehr fischig, Sie sollten den größeren Problemkontext beschreiben, da es so aussieht, als ob Sie nicht in dieser Situation sein sollten. Das heißt:

type MyType1 = A | B of float 
type MyType2 = C | D of int 

// imagine this adds floats, and C -> A (=0.0) and D -> B 
let DoIt x y = 
    match x, y with 
    | A, A -> 0.0 
    | A, B z -> z 
    | B z, A -> z 
    | B z1, B z2 -> z1 + z2 

let Convert x = 
    match x with 
    | C -> A 
    | D i -> B (float i) 

let Func (x:obj) (y:obj) = 
    match x, y with 
    | (:? MyType2 as xx), (:? MyType2 as yy) -> DoIt (Convert xx) (Convert yy) 
    | (:? MyType1 as xx), (:? MyType2 as yy) -> DoIt xx (Convert yy)  
    | (:? MyType2 as xx), (:? MyType1 as yy) -> DoIt (Convert xx) yy 
    | (:? MyType1 as xx), (:? MyType1 as yy) -> DoIt xx yy  
    | _ -> failwith "bad args" 
+0

Größerer Kontext: Ich habe es mit "Punktmengen" zu tun, die leer sein können, einzelne Punkte, Linien oder Vereinigungen von all diesen: 'type PSet = Leer | Einzelpunkt | Linie von Punkt * Richtung | Union der PSet-Liste ". Ich möchte in der Lage sein, eine Linie aus 2 Punkten zu machen, sie können entweder als zwei "Punkte" oder in einem "Single (Punkt)" eingewickelt werden. –

+0

Rechts. Ihre 'makeLine'-Funktion sollte wahrscheinlich zwei Punkte haben, und Clients, die' Single 'haben, sollten sie auspacken, bevor' makeLine 'aufgerufen wird. Oder verwenden Sie möglicherweise Überladung oder haben Sie eine separate Funktion (z. B. "makeLineFromSingles"). Tu nicht, was du tust, du wirfst statische Sicherheit weg und machst den Code unnötig kompliziert. Von einem OO-Hintergrund aus ist es verlockend, eine Reihe von Überladungen zu erzeugen, die ähnliche Daten "massieren", um sie in die richtige Form zu bringen. Bekämpfe diese Tendenz und du wirst besser davonkommen. – Brian

+0

Lol. Ja, du hast recht, ich denke in OO (wie ich es schon seit vielen Jahren gemacht habe) zu viel. Es könnte gesund sein, Dinge anders zu lernen! –

3

Ich habe zwei ähnliche, aber deutlich, Typen. Ich kann sehr einfach einen Typ in einen anderen konvertieren. Ich möchte eine binäre Operation definieren, die auf Entities dieser Typen wirkt, aber ich möchte , um eine einzige Operation zu dem -Client verfügbar machen.

Diese Funktion sollte entscheiden, welche von die opXY, Typen richtig zu canceln.

Dies ist einer der Fälle, in denen die richtige Antwort nicht "Hier ist, wie Sie es tun" lautet, sondern "tu es nicht so". Es gibt keinen wirklichen Vorteil bei der Verwendung von F #, wenn Sie nicht bei seinen Idiomen und Type-Checkern bleiben.

Aus meiner Sicht, wenn Ihr Typen so ähnlich sind, sollten sie in der gleichen Art zusammengefasst werden:

type MyType = A | B of float | C | D of int 

Abgesehen davon, können Sie Ihre zwei Typen in einer anderen Art wickeln:

type composite = 
    | MyType1Tuple of MyType1 * MyType1 
    | MyType2Tuple of MyType2 * MyType2 

Eine andere Ebene der Indirektion verletzt nie jemanden. Aber jetzt können Ihre Kunden die Objekte mit einer anderen umhüllen, ohne die Sicherheit zu verlieren.

Und abgesehen davon, legen Sie zwei separate Methoden für Ihre verschiedenen Arten.

6

Es gibt im Wesentlichen drei verschiedene Möglichkeiten, dies zu erreichen.

Die erste ist von Upcasting zu obj statische Typisierung zu opfern, wie Sie vorgeschlagen:

let func x y = 
    match box x, box y with 
    | (:? MyType1 as x), (:? MyType1 as y) -> 
     ... 

Diese praktisch immer eine schreckliche Lösung ist, weil es bei der Einführung von unnötigen Laufzeittypprüfungen Ergebnisse:

| _ -> invalidArg "x" "Run-time type error" 

Die nur Ort Ich habe diese Arbeit gut gesehen ist Bibliothekscode speziell für Benutzer aus F # interaktive Sitzungen, wo Kompilierzeit und Laufzeit Typ er aufrufen effektiv gleichzeitig auftreten und dynamische Typisierung kann prägnanter sein. Zum Beispiel ermöglicht unsere Bibliothek F# for Visualization dem Benutzer zu versuchen, jeden Wert eines beliebigen Typs mit dieser Technik zu visualisieren, um benutzerdefinierte Visualisierungsroutinen für verschiedene (bekannte) Typen aufzurufen (read more).

Die zweite ist Ihre zwei getrennten Typen mit einer einzigen Art zu ersetzen:

type MyType1 = A | B of float | C | D of int 

Die dritte Lösung ist eine neue Art einzuführen, die nur diese beiden Arten vereint:

type MyType = MyType1 of MyType1 | MyType2 of MyType2