2010-05-20 4 views
12

Warum wird t.b bei jedem Aufruf ausgewertet? Und gibt es einen Weg, wie man es nur einmal bewerten kann?F # Datensatzelement Auswertung

type test = 
    { a: float } 
    member x.b = 
    printfn "oh no" 
    x.a * 2. 

let t = { a = 1. } 
t.b 
t.b 
+0

Es ist enttäuschend, dass die F # -Sprache nicht einmal berechnete Werte für unveränderliche Datensätze unterstützt. Ich nehme an, die Komplikation ist, wenn "a" als änderbar markiert wird. – Wally

Antwort

12

Es ist eine Eigenschaft; Sie rufen im Grunde das get_b() Mitglied.

Wenn Sie die Wirkung wollen mit dem Konstruktor einmal passieren, könnten Sie eine Klasse verwenden:

type Test(a:float) = 
    // constructor 
    let b = // compute it once, store it in a field in the class 
     printfn "oh no" 
     a * 2. 
    // properties 
    member this.A = a 
    member this.B = b 
+0

Sie haben Recht, aber mit Klassen verliere ich Dinge wie c = {t mit a = 4.}, oder? –

+2

Ja, aber Sie können einen Konstruktor mit optionalen Parametern schreiben und erhalten einen sehr ähnlichen Effekt. – Brian

+1

Ich verstehe Ihre Idee nicht. Stellen Sie sich vor, dass ich einen Record mit Konstruktor mit 10 Parametern wie {a: float; b: float, c: float ...}. Das Erstellen eines neuen Datensatzes aus einem alten Datensatz erfolgt als {alt mit c = 5}. Wie mache ich dasselbe mit Klassen, ohne alle Parameter im Konstruktor umzuschreiben? –

14

Eine alternative Version von Brians Antwort, die b höchstens einmal bewerten, sondern wertet es überhaupt nicht wenn über B nie

type Test(a:float) = 
    // constructor 
    let b = lazy 
       printfn "oh no" 
       a * 2. 
    // properties 
    member this.A = a 
    member this.B = b.Value 
4

Als Antwort auf Ihre Kommentare in Brians Post verwendet wird, können Sie gefälschte Kopie-und-Verbuchungssatz Ausdrücke optional/named args verwenden. Zum Beispiel:

type Person(?person:Person, ?name, ?age) = 

    let getExplicitOrCopiedArg arg argName copy = 
     match arg, person with 
     | Some(value), _ -> value 
     | None, Some(p) -> copy(p) 
     | None, None -> nullArg argName 

    let name = getExplicitOrCopiedArg name "name" (fun p -> p.Name) 
    let age = getExplicitOrCopiedArg age "age" (fun p -> p.Age) 

    member x.Name = name 
    member x.Age = age 

let bill = new Person(name = "Bill", age = 20) 
let olderBill = new Person(bill, age = 25) 

printfn "Name: %s, Age: %d" bill.Name bill.Age 
printfn "Name: %s, Age: %d" olderBill.Name olderBill.Age 
0

Die bisherigen Reaktionen auf eine Klasse empfehlen, Schalten, anstatt einen Datensatz zu verwenden. Wenn Sie mit Datensatz bleiben wollen (für ihre einfache Syntax und Unveränderlichkeit), können Sie diesen Ansatz:

type test = 
    { a : float 
     b : float } 
    static member initialize (t: test) = 
     { t with b = t.a * 2. } 

Dies ist nützlich, wenn die Instanz von test von einer anderen Bibliothek erstellt wird (wie ein Datenprovider von einem Web Dienst oder Datenbank). Bei diesem Ansatz müssen Sie daran denken, jede Instanz von test, die Sie von dieser API erhalten, über die Initialisierungsfunktion zu übergeben, bevor Sie sie in Ihrem Code verwenden.