Oldrich Svec
Oldrich Svec

Reputation: 4231

F# record member evaluation

Why is t.b evaluated on every call? And is there any way how to make it evaluate only once?

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

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

Upvotes: 13

Views: 2341

Answers (4)

Wallace
Wallace

Reputation: 17419

The previous responses suggest switching to a class, instead of using a record. If you want to stay with records (for their simple syntax and immutability), you can take this approach:

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

This is useful if the instance of test is created by another library (like a data provider from a web service or database). With this approach, you must remember to pass any instance of test that you receive from that API through the initialize function before using it in your code.

Upvotes: 2

Daniel
Daniel

Reputation: 47904

In response to your comments in Brian's post, you can fake copy-and-update record expressions using optional/named args. For example:

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

Upvotes: 4

Ganesh Sittampalam
Ganesh Sittampalam

Reputation: 29100

An alternative version of Brian's answer that will evaluate b at most once, but won't evaluate it at all if B is never used

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

Upvotes: 17

Brian
Brian

Reputation: 118865

It's a property; you're basically calling the get_b() member.

If you want the effect to happen once with the constructor, you could use a class:

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

Upvotes: 13

Related Questions