2010-05-20 10 views
12

¿Por qué se evalúa t.b en cada llamada? ¿Y hay alguna forma de hacer que se evalúe solo una vez?F # evaluación de miembro de registro

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

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

Es decepcionante que el lenguaje F # no sea compatible con los valores calculados de una sola vez para los registros inmutables. Supongo que la complicación es si 'a' está marcado como mutable. – Wally

Respuesta

12

Es una propiedad; Básicamente estás llamando al miembro get_b().

Si desea que el efecto ocurra una vez con el constructor, se puede usar una clase:

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

Tienes razón, pero usando clases pierdo cosas como let c = {t con a = 4.}, ¿verdad? –

+2

Sí, pero puede escribir un constructor con parámetros opcionales y obtener un efecto muy similar. – Brian

+1

No entiendo tu idea. Imagine que tengo un Record con un constructor que tiene 10 parámetros como {a: float; b: float, c: float ...}. Crear un nuevo registro de uno antiguo se hace como {antiguo con c = 5}. ¿Cómo hago lo mismo con las clases sin reescribir todos los parámetros en el constructor? –

14

Una versión alternativa de la respuesta de Brian que evaluará b como máximo una vez, pero no lo evaluará en todos si no se usa nunca B

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

en respuesta a sus comentarios en el post de Brian, que se puede fingir récord de copiar y actualización de las expresiones que utilizan argumentos opcionales/con nombre. Por ejemplo:

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

Las respuestas anteriores sugieren cambiar a una clase, en lugar de usar un registro. Si desea quedarse con los registros (por su sencilla sintaxis y la inmutabilidad), puede tomar este enfoque:

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

Esto es útil si la instancia de test es creado por otra biblioteca (como un proveedor de datos de una web servicio o base de datos). Con este enfoque, debe recordar pasar cualquier instancia de test que reciba de esa API a través de la función de inicialización antes de usarla en su código.