strangelights.com

Main F# Site

Edit Edit
Print Print
Recent Changes Recent Changes
Subscriptions Subscriptions
Lost and Found Lost and Found
Find References Find References
Rename Rename
Search
List all versions List all versions
Object Model Syntax Examples
.
Summary
Core F# mostly follows core CAML (well known syntax - see CoreLanguageSyntaxExamples). The F# object model syntax is less familiar. Examples follow.
Todo

Covers Instance members, Static members, Signatures for members, Post-hoc augmentations, Properties, Indexer properties, Instances of overloaded operators, Interface implementations, Overriding methods on System.Object, Events, Instance and static members on class types, Inheritance abstract members, Inheritance default members, Inheritance override members, Casting.

Records

Many simple objects can be modelled as records, e.g.

 type Point = { x:int; 
                y:int }

These can also have state:

 type MutablePoint = { mutable x:int; 
                       mutable y:int }

Many quite sophisticated behavioural objects can be modelled using records with function-valued members.

 type PrintItem= { getNextChunk : unit -> string;
                   Format : string }

Records can also be used as the underlying storage for a type, where you only reveal a set of "members" associated with the type that give it a more object-oriented feel, with a richer set of surface operations. There are many examples below. The record representation of a type can be hidden in a signature, e.g. a signature may have

 type PrintItem= { getNextChunk : unit -> string;
                   Format : string }

or just

 type PrintItem

This applies to all record, discriminated union and class types. Some items may not be hidden.

Instance methods

Augmenting types with dot-notation property members is common in F#, even for types that are discriminated unions or records, or for abstract types that don't reveal anything else about the implementation.

  type Data = 
      { first: string;
        second: string; }
      with 
          member x.First = x.first
          member x.Second = x.second
          member x.Together = x.first + x.second
      end

Assuming you're using the #light syntax option, you can omit with/end if the members align with the first brace:

  #light
  type Data = 
      { first: string;
        second: string; }
      member x.First = x.first
      member x.Second = x.second
      member x.Together = x.first + x.second

Likewise discriminated unions can have members:

  #light
  type Data = 
      | OneThing of int
      | AnotherThing of string
      member x.Kind = 
          match x with 
          | OneThing _ -> "OneThing"
          | AnotherThing _ -> "AnotherThing"

Static methods

Augmenting non-class and abstract types with static property members is a little less common, but here's an example that represents a common idiom:

  #light
  type Data = 
      { first: string;
        second: string; }
      static member Defaults = { first = "1st"; second = "2nd" }


  let x = Data.Defaults
  let y = { Data.Defaults with first = "First" }

Or for an enum-like data structure:

  #light
  type Flags= 
      | Flags of int32
      static member LowMask  = 0x00000003
      static member HighMask = 0xFFFFFFFD
      static member A        = Flags 0x00000001
      static member B        = Flags 0x00000002
      static member C        = Flags 0x00000003


  let x = Flags.A 
  let y = Flags.B

Properties

Properties with basic get and set operations can be defined and accessed in the following manner. This example uses a field as a backing store:

  #light
  type MyObject = 
      { mutable height: int; mutable length: int }
      member x.Height with get() = x.height
                       and set v = x.height <- v


  let r (p : MyObject) r = p.Height <- p.Height + r

Indexer properties

Syntax for defining an indexer property (which is really an "Item" property) is given below

This first example has both get and set, and is indexed by one string, using a backing .NET hashtable dictionary:

  #light
  open System.Collections.Generic
  type CTest = 
      { data : Dictionary<string,float> }
      member x.Item 
         with get(a)   = x.data.[a]
         and  set(a) v = x.data.[a] <- v 

This example has get and set, is indexed by two strings, and uses a backing .NET hashtable dictionary (note the use of structural identity for hashing to ensure the .NET collection type correctly hashes the tuple keys)

  open System.Collections.Generic
  type CTest = 
      { data : Dictionary<(string * string),float> }
      member x.Item 
         with get(a,b)   = x.data.[(a,b)]
         and  set(a,b) v = x.data.[(a,b)] <- v 

Using an indexer property:

 let f (x:CTest) = x.["2","3"]

Defining a dispatch slot (actually a get/set pair of dispatch slots) which is accessed as an indexer property:

 type ITest = interface
   abstract Item : string * string -> float with get,set
 end

Creating an object that has a dispatch slot (note, the 'member' syntax will one day be supported for object expressions as well):

 let x = { new ITest with get_Item(a,b) = 1.0 and set_Item(a,b,v) = () }

Making a class (or other augmentable type) subscribe to the interface:

 type CTest2 = 
   class
       member x.Item with get(a,b) = 1.0 and set(a,b) v = () 
   end

Implementing an abstract slot (note, in F# 1.1.11.7 there is a type checking bug that means you have to use explicit get/set overrides)

 type CTest = 
   class
     interface ITest with 
       member x.get_Item(a,b) = 1.0
       member x.set_Item(a,b,v) = () 
     end
 end

Operator overloading

Instances of operator overloads may be defined on abstract data types using augmentations:

  type BigInt 
    with 
      static member ( + )(n1,n2) = RawBigIntOps.add n1 n2
      static member ( * )(n1,n2) = RawBigIntOps.mul n1 n2
      static member ( - )(n1,n2) = RawBigIntOps.sub n1 n2 
      static member ( / )(n1,n2) = RawBigIntOps.div n1 n2
      static member ( ~- )(n1)  = RawBigIntOps.neg n1
      static member ( ~+ )(n1:BigInt) = n1
    end

Here's another example:

  type Flags= Flags of int32
    with 
      static member (+) ((x:Flags),(y:Flags))  =
          match x,y with Flags xbits,Flags ybits -> Flags(xbits ||| ybits)
    end


  let x = Flags.A + Flags.B

Events

A class can expose an event which takes argument(s) 'a as follows:

  #light


  type MyWidget() = 
    let fireTick,tickEvent : tick = IEvent.create<string>()
    member x.Tick = tickEvent

Then to trigger the event from inside the widget with arguments of type string the widget can call

  fireTick "Tick"
  fireTick "Tock"

And a client can access x.Tick adding handlers to it etc.

Todo
Todo

Interface implementations

All concrete types may add discoverable interfaces to the semantics of values of that type. All interfaces must be listed with the type definition:

  #light


  type BigInt = 
      { sign : int; v : BigNatOps.n }
      interface System.IComparable
      interface IStructuralHash

(As it happens, record and union types implicitly implement the above two interfaces, as explained in the F# manual, but if you wish to override the default implementations inserted by the F# compiler you have to use an interface subscription).

  #light


  type BigInt with 
      interface System.IComparable with 
         member x.CompareTo(y:obj) = RawBigIntOps.compare x (y :?> BigInt)


      interface IStructuralHash with 
         member x.GetStructuralHashCode(nodesRemaining) = RawBigIntOps.hash(x)

Overriding methods

With the exception of interfaces, the members above are NOT abstract, that is, the member has one implementation, full stop. Virtual members are ones where different types or different object expressions give different implementations. Four abstract methods exist on the type 'object', from which all F# types derive. These can be overriden, e.g. as follows:

  #light


  type BigInt with 
      override x.ToString() = RawBigIntOps.to_string x
      override x.GetHashCode() = RawBigIntOps.hash(x)

The other two methods are 'Finalize' and 'Equals'. It is NOT recommended that you override 'Equals' for F# types. Instead, either rely on the structural comparison implementation automatically inserted by the compiler for your type or explicitly implement System.IComparable.

Object expressions

One can easily create "anonymous" implementations of interfaces in F# - if myfunction : IComparable -> unit then

  myfunction({ new IComparable with x.CompareTo(y:obj) = RawBigIntOps.compare x (y :?> BigInt) })

creates such an implementation as an anonymous object and passes it as a parameter to myfunction. It is possible to also do similarly to classes overriding abstract methods.

Signatures for members

The signature for such a type can, for example, hide the implementation but reveal some or all of the members:

  #light


  type Data with 
      member First : string
      member Second : string
      member Together : string
      member Property : string with get,set
      member Method : int -> string
      static member Defaults: Data

Defining augmentations after-the-fact

Augmentations can be defined after-the-fact, i.e. added to the information associated with a particualr type definition in the current scope. At the time of writing (F# 1.1.11) this could only be done in the same file as the type definition. A common idiom is to define the "representation" of a type, followed by a module containing the functional (hence beautiful and coherent) implementation of the operations on the type, followed by the augmentation of the type:

  #light


  type BigInt = { sign : int; v : BigNatOps.n }


  module BigIntOps = 
     let neg z  = {z with sign = -1 * z.sign}
     let abs x  = if x.sign = -1 then neg x else x
     // more ops here




  type BigInt with 
      override x.AbsoluteValue = abs x

Class types

The following examples show how to use class types. You'll notice:

  1. Fields (declared using 'val', initialized in constructors using record-like notation, and accessed via properties.)
  2. Constructors.
  3. Instance methods.
  4. Instance properties - get only, get and set, and indexed.
  5. Static methods.
  6. Static properties.
  7. Interface implementation.
  8. Overloading on arity (number of arguments.)
  9. Methods take tupled arguments.

First this example uses the "implicit class construction syntax":

  #light


  type point(name:string,x:float,y:float) =
      let mutable alpha = 0.0 // internal mutable state


      // Constructors
      new() = point("origin",0.0,0.0)      


      // Instance methods
      member p.Distance(x0,y0)   = let sqr x = x*x in
                                   sqrt(sqr(x-x0) + sqr(y-y0))
      member p.Distance(p:point) = p.Distance(x,y)


      // Properties, in this case publishing the construction arguments
      member p.X = x
      member p.Y = y
      member p.Name = name


      // Instance properties: get, get-and-set, indexed
      member p.Magnitude = p.Distance(0.0,0.0)       // property with get
      member p.Alpha                                 // property with get,set
        with get() = alpha
        and  set v = alpha <- v
      member p.Coord
        with get (i)   = if i=0 then x else        // indexer property getter
                         if i=1 then y else
                         failwith "point.Coord(i) had i>1"


      // Static methods
      static member unit(x) = new point("rotatedUnit",cos x,sin x)


      // Static property 
      static member origin = new point()


      // Interface implementation
      interface System.IComparable with 
         member p.CompareTo(q:obj) = let q = q :?> point in
                                     if (p.X > q.X) then 1 else
                                     if (p.X < q.X) then -1 else
                                     if (p.Y > q.Y) then 1 else
                                     if (p.Y < q.Y) then -1 else 0
  let p  = new point ("A",1.0,2.0)
  let n  = p.Name
  let x  = p.X
  let y  = p.Y
  let dA = p.Magnitude
  let dB = p.Distance(1.0,1.0)
  let dZ = p.Distance(p)
  let i  = point.unit(0.0)
  let j  = point.unit(System.Math.PI / 2.0)
Todo

This uses the more explicit syntax:

  #light
  type point =
    class
      val name : string             // gives ro property
      val mutable x : float         // gives rw property etc...
      val mutable y : float
      val mutable alpha : float


      // Constructors
      new() = {name = "origin"; x = 0.0; y = 0.0; alpha = 0.0}      
      new(nm,xx,yy) = {name = nm; x = xx; y = yy; alpha = 0.0}


      // Instance methods
      member p.Distance(x0,y0)   = let sqr x = x*x in
                                   sqrt(sqr(p.x-x0) + sqr(p.y-y0))
      member p.Distance(p:point) = p.Distance(p.x,p.y)


      // Instance properties: get, get-and-set, indexed
      member p.Magnitude = p.Distance(0.0,0.0)       // property with get
      member p.Alpha                                 // property with get,set
        with get() = p.alpha
        and  set x = p.alpha <- x
      member p.Coord
        with get (i)   = if i=0 then p.x else        // indexer property getter
                         if i=1 then p.y else
                         failwith "point.Coord(i) had i>1"
        and  set (i,v) = if i=0 then p.x <- v else   // indexer property setter
                         if i=1 then p.y <- v else
                         failwith "point.Coord(i,x) <- v had i>1"


      // Static methods
      static member unit(x) = new point("rotatedUnit",cos x,sin x)


      // Static property 
      static member origin = new point()


      // Interface implementation
      interface System.IComparable with 
         member p.CompareTo(q:obj) = let q = q :?> point in
                                     if (p.x > q.x) then 1 else
                                     if (p.x < q.x) then -1 else
                                     if (p.y > q.y) then 1 else
                                     if (p.y < q.y) then -1 else 0
      end 


      // Static class constructor -- not supported as of F# 1.1.11.11
      // static new() = ()
    end


  let p  = new point ("A",1.0,2.0)
  let n  = p.name
  let x  = p.x
  let y  = p.y
  let dA = p.Magnitude
  let dB = p.Distance(1.0,1.0)
  let dZ = p.Distance(p)
  let i  = point.unit(0.0)
  let j  = point.unit(System.Math.PI / 2.0)

Inheritance

  1. abstract members
  2. default members
  3. override members
  #light


  type seq() =
      abstract step : unit -> unit
      abstract x    : int
      abstract Text : string
      default  s.Text = s.x.ToString() ^ "..."


  type natseq(start) =
      inherit seq() as base
      let i = start
      override ns.step() = i <- i + 1
      override ns.x      = i


  let ns = new natseq()

Casting

In F#, objects are not always "automatically" cast to their superclass when calling methods (called "subsumption"). For example, if function Foo is a SuperBar -> unit and SubBar inherits SuperBar and r is a SubBar then

  Foo(r)

causes a compile time type error, whilst

  Foo(r :> SuperBar)

would not. This also applies to interfaces - to call r.CompareTo one would have to call

  (r :> IComparable).CompareTo

Casting operators are defined in the F# manual: The above cast :> is useful for static upcasts (e.g. to superclasses.) To dynamically downcast one can use e :?> ty for a specific type ty, or simply downcast e which will dynamically downcast e to a type inferred by the context.

You can define a function whose arguments have flexible types, e.g.

  let Foo (x: #SuperBar) = ...

In this case Foo(r) would be OK. Also, when calling object members subsumption is always applied.

Welcome to F Sharp Wiki, view the HomePage

This site supports the new NoFollow anti-spam initiative.

Recent Topics

Copyright 2005, Robert Pickering (Others where credited) | Terms of Use