Objects in OCaml
Classes, Objects, and Methods
Object Oriented Programming in OCaml is class-based. Classes define the fields and methods that all objects of that class share. If the class is lamps, then all lamps should probably have the same data fields (but probably not the same data) and the same methods. So in order to have objects, we should probably define the classes that those objects belong to.
Object Oriented Programming in OCaml is class-based. Classes define the fields and methods that all objects of that class share. If the class is lamps, then all lamps should probably have the same data fields (but probably not the same data) and the same methods. So in order to have objects, we should probably define the classes that those objects belong to.
Declaring Classes
Here's the general syntax for declaring a class and the class's interface. A class's interface is simply the names and types of the fields and methods a class contains.
class class_name parameters = |
Instance Variables/fields can be declared two ways with the val keyword
val value_name = expression The mutable keyword means that that field can be changed. (We've moved away from the side effect free environment of functional programming). Instance variables/fields without the mutable keyword cannot be changed.
val mutable value_name = expression Methods can be declared with the method keyword.
method method_name parameters = expression We can use keywords other than val and method in our declaration of classes, but we'll get to those later.
|
Here's an example of a 3-d point class. We should probably decide what arguments the 3-d point class needs.
We probably need x, y, and z values.
Those values should probably be stored in fields.
We should probably have a method that returns those values, a method that moves the point, and a method that computes the distance from the origin.
We probably need x, y, and z values.
Those values should probably be stored in fields.
We should probably have a method that returns those values, a method that moves the point, and a method that computes the distance from the origin.
class point x_val y_val z_val =
object
val mutable x_pos = x_val
val mutable y_pos = y_val
val mutable z_pos = z_val
method get_pos = (x_pos, y_pos, z_pos)
method move_pos x_val y_val z_val =
x_pos <- x_val;
y_pos <- y_val;
z_pos <- z_val
method distance () =
sqrt (float_of_int (x_pos*x_pos + y_pos*y_pos + z_pos*z_pos))
end
;;
class point :
int ->
int ->
int ->
object
val mutable x_pos : int
val mutable y_pos : int
val mutable z_pos : int
method distance : unit -> float
method get_pos : int * int * int
method move_pos : int -> int -> int -> unit
end
A couple of notes. We use the <- syntax to change mutable values. This probably should have been covered here... (Note to self). Also, some methods we require the unit parameter (distance) and others we do not (get_pos). This parameterless method (get_pos) method is typically used when accessing instance variables rather than doing operations on them.
Instantiating Objects
Now that we have this general point class, we don't actually have any point objects to play around with. We need to instantiate a point object, also called an instance. We do this using the new keyword followed by the class name.
let point1 = new point 1 2 3;; val point1 : point = <obj> |
We've previously covered how to declare classes in OCaml and for a problem involving these 3d points, we can probably accomplish it using classes. But OCaml tells us something very different about objects when we create an instance of them: We get very little information about them. This is extremely useful when we are hiding implementation details later on.
|
Invoking Methods
Now that we have an actual object, with actual information stored, we can actually do things with it. Let's find out where it's actually located. How do we invoke methods? We use the # keyword in conjunction with the object name.
point1#get_pos;; - : int * int * int = (1, 2, 3) |
Note that we can't do point1#x_pos. We can only invoke methods; this is why we need a method that reveals the position of the point. |
point1#move_pos 10 10 10;;
- : unit = ()
point1#get_pos;;
- : int * int * int = (10, 10, 10)
point1#distance ();;
- : float = 17.3205080756887746
So we've seen how to declare classes, instantiate objects of those classes, and invoke methods on those objects which is the core of class-based OOP in any language, even if the syntax is a little different.
Self
So what happens when we have a method that wants to call another method? Let's take a look at a trivial example!
class exampleclass input1 =
object
val mutable input1 = input1
method method1 () = input1
method method2 () = (method1 ()) ^ " Something!"
end
;;
Error: Unbound value method1
Ouch. Errors. It doesn't recognize method1 even though its declared the line above. Much like rec before it, in order for a method to call a method in the same class, we need a couple of new keywords.
class exampleclass input1 =
object (self)
val mutable input1 = input1
method method1 () = input1
method method2 () = (self#method1 ()) ^ " Something!"
end
;;
class exampleclass :
string ->
object
val mutable input1 : string
method method1 : unit -> string
method method2 : unit -> string
end
Great, so what did we do? We added the (self) keyword. We also use the # to call the method. It's akin to instantiating a pseudo-object of the class to be self and invoking a method defined in the class of self. Awesome, we can call methods inside the definition of methods in the same class.
Please note that the use of the word self is purely conventional. OCaml will accept any name there. So like you could potentially use (mrglglglglglr) as the relevant keyword if you wanted. It'd be annoying, but you could.
Please note that the use of the word self is purely conventional. OCaml will accept any name there. So like you could potentially use (mrglglglglglr) as the relevant keyword if you wanted. It'd be annoying, but you could.
Private Methods
Now that we've covered how to call the methods we've defined in the class in our interface. What if we want some helper functions that are only accessed in our interface but not when we are calling methods on object instances. Why would we ever want to write code to not allow ourselves to use it later? Reasons.
These methods that are only available to ourselves during the definition of the interface and not via objects are called private. We will define them using the private keyword and call it via self#, while preventing it from being
These methods that are only available to ourselves during the definition of the interface and not via objects are called private. We will define them using the private keyword and call it via self#, while preventing it from being
class exampleclass input1 =
object (self)
val mutable input1 = input1
method method1 () = input1
method private hidden_method () = "something something something"
method method2 () = (self#method1 ()) ^ " Something!" ^ (self#hidden_method ())
end
;;
class exampleclass :
string ->
object
val mutable input1 : string
method private hidden_method : unit -> string
method method1 : unit -> string
method method2 : unit -> string
end
let example_object = new exampleclass "example input";;
example_object#hidden_method ();;
Error: This expression has type exampleclass
It has no method hidden_method
Object Equality
Finally, we encounter one additional detail about objects in OCaml, equality. Equality in OCaml asks whether they are the literal same object, not whether they have the same values.
let point2 = new point 10 10 10;;
point1 = point2;;
- : bool = false
let point2 = point1;;
point1 = point2;;
- : bool = true
Next, we'll take a look at relationships between classes, including the key relationship in OOP, inheritance.
Relevant links:
http://caml.inria.fr/pub/docs/oreilly-book/pdf/chap15.pdf
http://php.net/manual/en/language.oop5.interfaces.php
http://caml.inria.fr/pub/docs/oreilly-book/pdf/chap15.pdf
http://php.net/manual/en/language.oop5.interfaces.php