Wednesday, February 18, 2009

[2009.02.18] F# Types: Records [I/III]

The Record type is the simplest concrete data structure in F# and is immutable by default. A record is defined using the type keyword, followed by the identifier for the record, an equals sign, braces and a list of labels for the record.

    1 #light

    2 #r "FSharp.PowerPack.dll"

    3 

    4 (*

    5 Example 1: A record type describing a

    6 programming language

    7 *)

    8 

    9 // define the record

   10 type Language =

   11     {   Name: string; // label:type

   12         Company: string;

   13         Age: int

   14     }

   15 

   16 // create a record and fill in fields

   17 let rec1 =

   18     {   Name = "F#";

   19         Company = "Microsoft";

   20         Age = 2

   21     }

   22 // the signature of rec1 is as follows:

   23 > val rec1 : Language

Cloning Records

F# provides a nice way to create a new record based on an existing record, but allows you to change just the necessary fields. You can think of it as the BasedOn command used in XAML Styles, where you can define a new style based on an existing one, just modifying the parts of it as needed. For instance, let's say that for some reason, as F# ages, we want to create a record to indicate this change.

   24 // Example 2: Cloning records

   25 // clone the original rec1 record

   26 //  and modify the Age field

   27 // Note that this reduces the work needed

   28 //  to be done to create rec2

   29 let rec2 = {rec1 with Age = 3}

   30 

   31 // notice that rec2 retains the information

   32 //  from rec1 while only changing the Age field

   33 > val it : Language =

   34     {   Name = "F#";

   35         Company = "Microsoft";

   36         Age = 3;}


[2009.02.17].01.Record.Type.Intellisense
Fig.1 Intellisense when using F# record type

Mutating Records

Recall that record types are immutable so once a record is created, it cannot be changed. However, if you want a record to be modified without creating a new instance of it, you can mark the necessary fields that is to be modified with the mutable keyword. In addition, the left operator <- and familiar dot notation can be used to modify a record's field. Only fields that have been marked as mutable can be changed, and any attempt to modify a non-mutable field results in a compiler error.

   37 //Example 3: Mutating a record

   38 // define the record

   39 type Language2 =

   40     {   Name : string; // label:type of label

   41         Company : string;

   42         mutable Age : int

   43     }

   44 

   45 let rec3 =

   46     {   Name = "F#";

   47         Company = "Microsoft";

   48         Age = 2

   49     }

   50 

   51 rec3;;

   52 > val it : Language2 = {Name = "F#";

   53                       Company = "Microsoft";

   54                       Age = 2;}

   55 

   56 // modify the Age field

   57 // can use dot notation and get intellisense

   58 rec3.Age <- 4

   59 > val it : unit = ()

   60 

   61 // show the mutated record

   62 rec3;;

   63 > val it : Language2 =

   64     {   Name = "F#";

   65         Company = "Microsoft";

   66         Age = 4;}

   67 

   68 // attempt to modify a non-mutable field

   69 rec3.Name <- "Microsoft"

   70 // will get the error:

   71  error FS0005: This field is not mutable

Methods, Properties, Indexers, Operator Overloading

As a type in F#, records lend themselves to a variety of augmentations available to types in the .NET world. Let me illustrate how some of these are implemented in F#.

    1 #light

    2 #r "FSharp.PowerPack.dll"

    3 

    4 type Complex =

    5     {  

    6     // create mutable fields so that they can

    7     //  be modified

    8         mutable re : float;

    9         mutable im : float   

   10     } with

   11 

   12     //[1] Magnitude property

   13     member c.Magnitude = 

   14         sqrt(c.re * c.re + c.im + c.im)

   15 

   16     //[2] static Unit property

   17     // create a zero based complex number

   18     static member Zero = {re = 0.; im = 0.}

   19 

   20     //[3] Indexer + Get/Set

   21     // allows you to index into the complex type

   22     //  where c.[0] will get/set the real part and

   23     //  c.[1] will get/set the imaginary part

   24     // this also demos the get/set methods used to

   25     //  update properties

   26     member c.Item

   27         with get(index) =

   28             match index with

   29             | 0 -> c.re

   30             | 1 -> c.im

   31             | _ -> invalid_arg "Complex.get"

   32         and set index value =

   33             match index with

   34             | 0 -> c.re <- value

   35             | 1 -> c.im <- value

   36             | _ -> invalid_arg "Complex.set"

   37 

   38     //[4] Operator Overloading

   39     static member (+) (c1:Complex, c2:Complex) =

   40         {   re  = c1.re + c2.re ;

   41             im = c1.im + c2.im }

   42 

   43     //[5] Display method

   44     // convert re and im to strings

   45     // and print to screen

   46     member c.Display() =

   47         string c.re + "," + string c.im  + "i"

   48 

   49 // the signature of the Complex type is:

   50 >

   51 type Complex =

   52   {mutable re: float;

   53    mutable im: float;}

   54   with

   55     member Display : unit -> string

   56     member Item : index:int -> float with get

   57     member Magnitude : float

   58     static member Zero : Complex

   59     static member ( + ) :

   60         c1:Complex * c2:Complex -> Complex

   61     member Item : index:int -> float with set

   62   end

   63 

   64 

   65 // let's take a look at how we use all

   66 //  these features

   67 let d = {re = 3.; im = 4.} // create complex

   68 let e = {re = 2.; im = 4.} // numbers to use

   69 

   70 //[1] Magnitude of d

   71 d.Magnitude;;

   72 > val it : float = 4.123105626

   73 

   74 //[2] Zero complex number

   75 let f = Complex.Zero

   76 f;;

   77 > val it : Complex = {re = 0.0;

   78                     im = 0.0;}

   79 

   80 //[3] Indexer

   81 // get the real part of e

   82 e.[0];; // get

   83 > val it : float = 2.0

   84 e.[0] <- 4.;; // set

   85 e;;

   86 > val it : Complex = {re = 4.0;

   87                     im = 4.0;}

   88 

   89 //[4] Operator overloading

   90 let c2 = d + e

   91 c2;;

   92 > val it : Complex = {re = 7.0;

   93                     im = 8.0;}

   94 

   95 //[5] Display method

   96 d.Display();;

   97 > val it : string = "3,4i"

NOTE: When working with Indexers, you need to explicitly use the Item member name or the compiler will throw an exception.

No comments: