In F#, you can defined record types which dif­fer from tuples and dis­crim­i­nated unions in that they allow you to orga­nize val­ues into a type and name those val­ues through fields:

image

Looks like a cut-down ver­sion of a stan­dard .Net class with a cou­ple of prop­er­ties? Yes they do, but records offer sev­eral advan­tages over the .Net classes.

Type Infer­ence

One of the biggest advan­tage records offer is that it works seam­lessly with F#‘s type infer­ence sys­tem. Whereas classes must be anno­tated in order to be used, record types can be inferred by the fields you access!

Con­sider this sim­ple function:

image

and see what its func­tion sig­na­ture reads:

image

Magic! Well, with a caveat :-P As you can imag­ine, if you define a sec­ond record type which con­tains the same set of fields then it might just con­fuses the com­piler a lit­tle. For instance, if you define a record type for employees:

image

The func­tion sig­na­ture for the print­Per­son func­tion will become:

image

So to tell the print­Per­son func­tion to use the Per­son record instead, you’ll need to anno­tate the para­me­ter p:

image

Immutabil­ity

Unlike classes, record fields are immutable by default. This immutabil­ity by default approach (zero side effect) is inline with every­thing else in F# and in func­tional lan­guages in gen­eral, and offers a degree of safety guarantee.

How­ever, whether immutabil­ity is an advan­tage very much depends on your use case, Eric Lip­pert made an excel­lent argu­ment here that the lack of side effects and immutable data struc­tures func­tion lan­guages offer are both advan­tages and disadvantages.

For­tu­nately, with F# record types, it’s pos­si­ble to change the field val­ues, either by using the copy and update record expres­sion:

image

or you can explic­itly spec­ify that a field should be mutable:

image

then you are allowed to update the value of the muta­ble fields:

image

No Inher­i­tance

Records can­not be inher­ited, which gives you a future guar­an­tee of safety but then again you can equally make argu­ments against it, another post another day maybe..

Pat­tern Matching

Records can be used as part of stan­dard pat­tern match­ing whereas classes can­not with­out resort­ing to active pat­terns or with when guards.

This is impor­tant, as you’re likely to be using pat­tern match­ing every­where in your code and frankly, why wouldn’t you? It’s so awe­some ;-) You can mach the struc­ture of records and not every record field needs to be a part of the pat­tern match:

image

Equally you can cap­ture val­ues and match literal:

image

Struc­tural Equal­ity Semantics

Stan­dard .Net classes have ref­er­ence equal­ity seman­tics (two ref­er­ence type vari­ables are deemed equal if they point to the same loca­tion in mem­ory), but records have struc­tural equal­ity seman­tics (two val­ues are deemed equal if their fields are equal):

image

In this respect, records are the same as .Net structs, which also struc­tural equal­ity seman­tics and can­not be inherited.

Update 27/09/2011:

One thing I for­got to men­tion is that whilst you can define a record type with just the list of fields, you can also add meth­ods and prop­er­ties using the mem­ber keyword:

image

Share

6 Responses to “F# — Record types vs classes”

  1. Yusuf Motara says:

    print­Per­son” prob­a­bly doesn’t do what you expect it to do. Remove the com­mas, and it will.

  2. theburningmonk says:

    @Yusuf Motara — well spot­ted, corrected

  3. Yusuf Motara says:

    The func­tion sig­na­ture will also change to “Per­son -> unit” ;).

  4. theburningmonk says:

    @Yusuf Motara — you sir, have a sharp eye ;-) thanks, that’s updated accord­ingly now

  5. […] F#, you have the choice of using a struct or a record as a light­weight con­tainer for data. The sim­i­lar­i­ties between the two are striking […]

  6. Matthew says:

    Nice code. What if I have a record where I don’t need to set all of the fields

    For exam­ple: let Per­son = { First­Name : string; Last­Name : string; Age : int; Died : DateTime }

    Then I would like to cre­ate a per­son record leav­ing out the Died field. I want to set that later using the “with” tech­nique. Com­piler will not let me do this unfortunately…

Leave a Reply