![]() |
Edit |
![]() |
|
![]() |
Recent Changes |
![]() |
Subscriptions |
![]() |
Lost and Found |
![]() |
Find References |
![]() |
Rename |
| Search |
![]() |
List all versions |
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.
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.
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"
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 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
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
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
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.
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)
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.
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.
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
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
The following examples show how to use class types. You'll notice:
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)
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)
#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()
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.
![]() |
| This site supports the new NoFollow anti-spam initiative. |
Recent Topics