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.
4 comments:
In F# please try this and see if it helps:
type MyRecord = private { x : int }
This makes the representation (not the type itself or its methods) private to the enclosing module.
Proper functional programming offers a lot better encapsulation with abstract types and the module system than any OO language ever did - see any of the tutorials on SML.
AS for dynamic dispatch, *any function* 'a -> 'b is doing it, in fact any function is, through OO eyes, a one-method interface, capable of having any state it wants if it is a closure. If you think of it, it is also encapsulating its state.
As for inheritance it is simply a bug. There are ways to get similar code reuse without breaking the type system. Unfortunately F# allows inheritance to be compatible with .NET.
That's great, I did not know you could put access modifiers there! Now I can just remove half of the blog post :)
Joh, in this case there's not much sense to do this, is there? Although adding members/interfaces to records/unions is occasionally useful.
Anton, uh, *fortunately* F# allows inheritance. It would be impossible to interface with many .NET components otherwise.
That's right, records don't bring much over classes, but discriminated unions do. I should have picked a better example.
Post a Comment