Methods are functions or procedures which are tightly coupled to a type of object.
Dynamic dispatch is the ability to execute different implementations of an operation depending on the concrete types of arguments. A special case which often has dedicated syntax is when the operation is a method, and the argument controlling the dispatch is the object itself.
Open recursion makes the object available to its methods through an identifier, typically called this or self.
Encapsulation restricts access to the internal data and operations of an object to a restricted area of the code. Typically this area is the methods of the object itself, F# works at the module level instead.
The code below shows how a vector type can be defined using records.
[< CustomComparison >] [< CustomEquality >] type Vector2 = private { x : float y : float } with // Constructors static member New(x, y) = { x = x; y = y } static member Zero = { x = 0.0; y = 0.0 } // Accessors member this.X = this.x member this.Y = this.y member this.Length = let sq x = x * x in sqrt(sq this.x + sq this.y) // Methods member this.CompareTo(other : obj) = match other with | null -> nullArg "other" | :? Vector2 as v -> this.CompareTo(v) | _ -> invalidArg "other" "Must be an instance of Vector2" member this.CompareTo(other : Vector2) = let dx = lazy (this.x - other.x) let dy = lazy (this.y - other.y) if dx.Value < 0.0 then -1 elif dx.Value > 0.0 then +1 elif dy.Value < 0.0 then -1 elif dy.Value > 0.0 then +1 else 0 // Overrides for System.Object override this.ToString() = sprintf "(%f, %f)" this.x this.y override this.Equals(other) = this.CompareTo(other) = 0 override this.GetHashCode() = (this.x, this.y).GetHashCode() // Explicit interface implementations interface System.IComparable<Vector2> with member this.CompareTo(other) = this.CompareTo(other) interface System.IComparable with member this.CompareTo(other) = this.CompareTo(other) interface System.IEquatable<Vector2> with member this.Equals(other) = this.CompareTo(other) = 0
The use of private on line 4 makes the representation (not the type itself or its methods) private to the enclosing module (Thanks to Anton for pointing that out in the comments).
Members can access the object through an identifer (this), and open recursion is covered.
Vector2 can be used as one would expect to be able to use an object, as illustrated below.
Dynamic dispatch is exercised by Array.sortInPlace (which calls CompareTo).
let playWithIt() = let v = Vector2.New(0.0, 2.0) let v2 = Vector2.New(v.y, -v.x) printfn "%s and %s have %s lengths" (v.ToString()) (v2.ToString()) (if (let epsilon = 1e-6 in abs(v.Length - v2.Length) < epsilon) then "close" else "different") let rnd = System.Random() let vs = [| for i in 1..10 -> Vector2.New(rnd.NextDouble(), rnd.NextDouble()) |] Array.sortInPlace vs printfn "%A" vs
I cannot write an entire article about OOP and not mention inheritance. F#-specific datatypes do not support inheritance, but I will try to show in a later post why this is not as big a problem as one might think.