2017-04-24 2 views
10

Wenn ich den funktionalen Ansatz der Definition eines Typs und der Beschreibung von Funktionen (im Gegensatz zu Instanzmethoden) verwende, die auf diesen Typ angewendet werden, wie soll ich meinen Code organisieren?F #: Best Practices zum Organisieren von Typen und Modulen

Ich gehe im Allgemeinen eine von drei Arten:

(1):

module MyType = 
    type MyType (x : int) = 
     member val X = x with get 

    let myFunction myType y = myType.X + y 

(2):

type MyType (x : int) = 
    member val X = x with get 

[<RequireQualifiedAccess>] 
module MyTypeOps = 
    let myFunction myType y = myType.X + y 

(3):

type MyType = 
    { 
     x : int 
    } 

    static member MyFunction myType y = myType.x + y 

Pro von (1) ist, dass Funktionen in einem Modul definiert sind. Nachteile von (1) sind, dass der Typ auch in dem Modul definiert ist, was zu hässlicher MyType.MyType Redundanz bei Instanziierung und der Unfähigkeit [<RequireQualifiedAccess>] führt, wenn Sie open MyType als Workaround für diese Redundanz zulassen wollen.

Vorteile von (2) sind in einem Modul definierte Funktionen und keine redundante module.type. Con ist, dass das Modul nicht den gleichen Namen wie der Typ haben kann.

Pro (3) ist, dass die Methoden statisch sind und keine module.type Redundanz. Nachteile sind, dass Sie einige Typen (z. B. Nicht-Methode-Werte und aktive Muster) unter dieser Methode nicht definieren können.

Normalerweise bevorzuge ich (2), obwohl ich es hasse, das Modul etwas zu nennen, das weniger beschreibend und intuitiv ist, als es sein sollte. (2) erlaubt mir auch gegenseitig abhängige Typen mit type ... and ... in dem seltenen Fall zu erstellen, dass ich das tun muss, was offensichtlich für Typen nicht möglich ist, die in separaten Modulen wie Ansatz (1) definiert sind.

Welchen Ansatz nehmen meine anderen F # -Programmierer? Ich frage mich, ob ich etwas offensichtlich übersehen habe (oder nicht so offensichtlich), oder, wenn nicht, ob es da draußen eine Konvention gibt, die die Unfähigkeit adressiert, einem Modul den gleichen Namen wie seinem entsprechenden Typ zu geben der gleiche Namespace.

Antwort

9

Es gibt eine vierte Möglichkeit, die Sie nicht aufgelistet haben, nämlich den Typ und das Modul, die gleich benannt sind. Sie denken, dass es nicht getan werden kann, aber es ist tatsächlich eine sehr übliche Praxis; Sie müssen einfach (in F # Versionen vor 4.1) das [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] Attribut auf dem Modul verwenden. Es ist ein bisschen hässlich, das überall zu schreiben, weshalb F# 4.1 made this the default behavior, wenn Sie einen Typ mit dem gleichen Namen wie ein Modul definieren. Sehen Sie sich den FSharpx.Collections-Code (neben vielen anderen Projekten) an, um zu sehen, wie es funktioniert. Hier ist eine Datei, die nicht zu horrend groß ist, die für ein gutes Beispiel macht:

https://github.com/fsprojects/FSharpx.Collections/blob/master/src/FSharpx.Collections/Queue.fs

namespace FSharpx.Collections 

type Queue<'T> (front : list<'T>, rBack : list<'T>) = 
    // ... 
    member this.Conj x = 
     match front, x::rBack with 
     | [], r -> Queue((List.rev r), []) 
     | f, r -> Queue(f, r) 
    // ... 

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] 
module Queue = 
    // ... 
    let inline conj (x : 'T) (q : Queue<'T>) = (q.Conj x) 
    // ... 

Wenn Sie Ihren Code auf diese Weise, mit einer Art und Modul mit dem gleichen Namen organisieren, die F # Der Compiler ist perfekt in der Lage, die Dinge klar zu halten - besonders, wenn Sie den Standardnamenskonventionen folgen, Membermethoden im PascalCase-Stil benannt haben, aber Funktionen in dem im camelCase-Stil benannten Modul haben. Der C# -Compiler würde verwirrt werden, aber das CompilationRepresentation-Attribut kümmert sich darum, sicherzustellen, dass andere .Net-Sprachen das Modul mit dem Namen QueueModule sehen.Also von F #:

let q = new Queue([1;2;3], []) 
let moreItems = q |> Queue.conj 4 
let stillMoreItems = moreItems.Conj 5 

Von C#:

Queue<int> q = new Queue<int>({1,2,3}, {}); // Not valid list syntax, but whatever 
Queue<int> moreItems = QueueModule.Conj(4, q); 
Queue<int> stillMoreItems = moreItems.Conj(5); 

Die Queue.conj Funktion sieht natürlicher aus F # zu verwenden, und das Element Methode moreItems.Conj sieht natürlicher aus C# zu verwenden, aber beide sind in beide Sprachen.

+1

Die [Sammlungstypen in der F # Standardbibliothek] (https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/FSharp.Core/list.fs) folgen auch dem Modul mit dem gleichen Name als Typ 'Muster' (allerdings ohne die entsprechenden Methoden an den Typ angehängt zu haben). –

+0

Interessante Wahl, mit diesem CompilationRepresentation Attribut (ich hatte das vorher nicht gesehen, danke für den Tipp). Ich gebe Ihnen das Häkchen für diese Antwort, da es eine großartige Antwort auf meine Frage ist, obwohl die C# Interoperabilität betroffen ist (was für mich ein Problem ist), es ist im Grunde das gleiche wie die zweite der drei Methoden, die ich aufgelistet habe . Plus, ich mag es nicht, Funktionalität in Member-Methoden zu programmieren; Ich bevorzuge das im entsprechenden Modul, da ich meine Typen gerne auf die Deklaration von Eigenschaften beschränke (und keine Funktionen an sie angehängt habe). – MiloDC

+0

Nur aus Neugierde, wenn Warteschlange ein Datensatztyp ist, den Sie von C# idiomatisch machen möchten (da ich dazu tendiere, meine Funktionen von meinen Typen zu trennen, verwende ich Datensätze eine LOT), wie definieren Sie dann aktive Muster Mitglied Methoden? – MiloDC

Verwandte Themen