2013-08-30 7 views
14

F # macht es einfach zu definieren Typen zu tun, wieWie Argument Validierung von F # Aufzeichnungen

type coords = { X : float; Y : float } 

aber wie kann ich definieren Einschränkungen/Check Argumente für den Konstruktor ohne in die ausführlichere Klassendefinition Syntax? Z.B. wenn ich möchte, dass Koordinaten von (0,0) beginnen oder eine Ausnahme auslösen.

Darüber hinaus, wenn ich meine Definition in eine Klasse ändern, muss ich Equals() usw. implementieren alle Kesselblechcode, den ich nicht will (und den ich in C# habe, dass ich versuche wegzukommen) .

+1

mögliche Duplikate von [Ist es möglich zu erzwingen, dass ein Record einige Invarianten respektiert?] (Http://stackoverflow.com/questions/13925361/is-it-possible-to-enforce-that-a-record-repects -some-Invarianten) –

+0

Dies ist ein Duplikat der anderen Frage. Wählen Sie, um zu schließen. –

Antwort

13

Sie können die Implementierung privat machen. Sie erhalten immer noch strukturelle Gleichheit, aber Sie verlieren direkten Feldzugriff und Mustererkennung. Sie können diese Fähigkeit mithilfe aktiver Muster wiederherstellen.

//file1.fs 

type Coords = 
    private { 
    X: float 
    Y: float 
    } 

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] 
module Coords = 
    ///The ONLY way to create Coords 
    let create x y = 
    check x 
    check y 
    {X=x; Y=y} 

    let (|Coords|) {X=x; Y=y} = (x, y) 

//file2.fs 

open Coords 
let coords = create 1.0 1.0 
let (Coords(x, y)) = coords 
printfn "%f, %f" x y 
+2

Das ist eine gute Problemumgehung Daniel. Ich wünschte, ich hätte es mir ausgedacht. –

+3

Danke, mein Bauchgefühl sagt mir, das ist sehr verworren und kein typischer Ansatz, um Typen herzustellen? Ich denke, eine Klasse zu machen und Equals zu implementieren ist vorzuziehen .. oder erzwinge, dass alle Typen in meinem Programm durch eine generische create Methode konstruiert werden? –

+0

Es ist selten, ja, aber nur so gewunden, wie Sie es machen. Ich denke, private Implementierung ist ein gutes Feature und nützlich für einige zugegebenermaßen ungewöhnliche Szenarien. – Daniel

4

Sie haben die Klassendefinition Syntax:

type coords(x: float, y: float) = 
    do 
    if x < 0.0 then 
     invalidArg "x" "Cannot be negative" 
    if y < 0.0 then 
     invalidArg "y" "Cannot be negative" 

    member this.X = 
    x 
    member this.Y = 
    y 
+2

Wenn ich meine Definition in eine Klasse ändere, muss ich Equals() usw. implementieren, den ganzen Kesselblech-Code, den ich nicht will (und den ich in C# habe, von dem ich versuche wegzukommen). –

+0

Ja, das musst du tun. Wenn Sie eine objektorientierte Kapselung/Datenverbergung benötigen, brauchen Sie objektorientiertes C# -ähnliches Zeug, um das geht es nicht. Das Schöne an F # ist, dass Sie es tun können. – MiMo

+4

also was wäre der F # Weg? Definieren Sie eine Klasse oder definieren Sie eine Erstellungsfunktion im selben Modul wie der Datensatz und hoffen Sie, dass die create-Methode verwendet wird? –

5

Es gibt eine Reihe Designing with Types auf F# for fun and profit genannt. Im Abschnitt "Erzwingen der Verwendung des Konstruktors" wird die Verwendung von Konstruktorfunktionen empfohlen - dort werden die Validierungen ausgeführt, bevor der Typ instanziiert wird. Um zu verhindern, dass Benutzer Typen direkt instanziieren, werden Namenskonventionen oder Signaturdateien empfohlen.

Sie können mehrere relevante Artikel und Beispiele finden, indem Sie "domain driven design f #" googeln.

Beachten Sie, dass ich aus C# komme/F # noch nicht auf unsere Domainebene angewendet habe (noch;) Ich kann nicht wirklich sagen, wie eine der empfohlenen Methoden in einem größeren Projekt funktionieren würde. Einige Dinge scheinen sicher anders in dieser schönen neuen Welt.