O'Caml for OO Programmers
Author |
Message |
wtd
|
Posted: Fri May 06, 2005 12:06 am Post subject: O'Caml for OO Programmers |
|
|
O'Caml: A Powerful Option for OO Programming
So far I've mostly talked up O'Caml on its merits as a functional language, but the "O" in the name is shorthand for "Objective" and O'Caml has powerful support for object-oriented programming, and offers some conveniences programmers who work in other OO languages would do well to heed.
Good Habits
Primary among O'Caml's benefits for OO programmers is the fact that all instance variables are private, and methods are public by default.
Differing from other languages, private methods and instance variables are inherited, but remain private. As such, there is no "protected" qualifier. Also handy is that private methods can be made public easily via a type signature.
Inheritance Convenience
Let's consider how we can expose protected methods from a parent class.
c++: | class foo
{
protected:
int baz()
{
return 42;
}
};
class bar : public foo
{
public:
int baz()
{
return foo::baz();
}
}; |
Now, a naive O'Caml scenario.
code: | class foo =
object (self)
method private bar = 42
end
class bar =
object (self)
inherit foo
method bar = foo#bar
end |
But even better, we can use a type signature for "self".
code: | class foo =
object (self)
method private bar = 42
end
class bar =
object (self : <bar : _; ..>)
inherit foo
end |
Naming Flexibility
O'Caml's class syntax doesn't lock you into a particular set of names. Let's say we want "this" as in C++ and Java.
code: | class foo =
object (this)
method private bar = 42
end
class bar =
object (this : <bar : _; ..>)
inherit foo
end |
Of course, it's also possible to ellide this entirely. Due to the signature we use in the "bar" class, this isn't possible.
code: | class foo =
object
method private bar = 42
end
class bar =
object (this : <bar : _; ..>)
inherit foo
end |
We also can't remove these names if we want to access methods from within methods.
code: | class baz =
object (self)
method wooble = 42
method ninja = self#wooble * 2
end |
The names of inherited classes can be aliased as well.
code: | class qux =
object (self)
inherit baz as super
method ninja = super#ninja + 1
end |
Other Good Habits: Instance Variables
Let's create a class with an instance variable. In this case it's a greeter class which contains an instance variable to hold the name of the person to greet. Methods are available to get a name, and to greet the person.
If a name hasn't been read, raise a No_name exception.
code: | exception No_name
class greeter =
object (self)
val name = ""
method prompt_for_name = print_string "You are? "
method read_name = name <- read_line ()
method greet =
if name = "" then
raise No_name
else (
print_string "Hello, ";
print_endline name )
end |
But there's a problem. Instance variables by default in O'Caml are immutable ("constant"). We need to very specifically make the instance variable mutable. This, though, is a very good thing. Many times, what programmers really want are constant values, and we often forget to include "const" or "final" where appropriate.
code: | exception No_name
class greeter =
object (self)
val mutable name = ""
method prompt_for_name = print_string "You are? "
method read_name = name <- read_line ()
method greet =
if name = "" then
raise No_name
else (
print_string "Hello, ";
print_endline name )
end |
Initializing Instance Variables
In that last example I simply set an instance variable equal to an empty string. Not a lot of flexibility there. So, how do I set some other value?
code: | class name init_first init_last =
object (self)
val first = init_first
val last = init_last
(* accessors *)
method first = first
method last = last
method full_name =
first ^ " " ^ last
end |
Now, we can create a new name.
code: | let bobs_name = new name "Bob" "Smith" |
And then we can print the full name.
code: | print_endline bobs_name#full_name |
Comments?
Feel free to ask any questions you may have. This is not by any means comprehensive. |
|
|
|
|
|
Sponsor Sponsor
|
|
|
wtd
|
Posted: Sat May 07, 2005 7:33 pm Post subject: (No subject) |
|
|
What's Missing?
It should be fairly obvious to anyone familiar with other OO languages that so far I have talked about constructors. Yes, I can pass arguments to a class at creation, but I can't do anything else with those values.
So let's look at a simple "bank account" class.
code: | class bank_account init_balance =
object (self)
val mutable balance = init_balance
method current_balance = balance
end |
The problem: what if we create a bank account with an initial balanace that's a negative number? We have no way of enforcing that yet, but we can enforce it, by using an initializer.
code: | class bank_account init_balance =
object (self)
initializer
if init_balance < 0 then
raise (Bad_balance init_balance)
val mutable balance = init_balance
method current_balance = balance
end |
Inheritance and Initializers
Initiaizers are inherited, sso, in this instance any class that inherits bank_account would have to enforce that condition. As such, it might be better to place this logic in a subclass.
code: | class bank_account init_balance =
object (self)
val mutable balance = init_balance
method current_balance = balance
end
class strict_bank_account init_balance =
object (self)
inherit bank_account init_balance as super
initializer
if init_balance < 0 then
raise (Bad_balance init_balance)
end |
The Benefit
The fact that initializers are inherited may seem to be an inconvenience, but it does allow for enforced conditions. Such strict enforcement is not in place in many other OO programming languages, especially if they use protected instance variables which behave the same way O'Caml's instance variables do. |
|
|
|
|
|
wtd
|
Posted: Wed May 11, 2005 6:01 pm Post subject: (No subject) |
|
|
Class Interfaces
In Java, and other programming languages, we have classes, but we also have interfaces, which only specify that a class implementing that interface must include certain things, but don't specify those methods themselves.
O'Caml has these as well.
Java: | interface Foo
{
public int bar ();
public String baz ();
} |
code: | class virtual foo =
object (self)
method virtual bar : int
method virtual baz : string
end |
An implementing class then has to have those methods.
Java: | class Bar implements Foo
{
public int bar () { return 42; }
public String baz () { return "hello"; }
} |
code: | class bar =
object (self)
inherit foo
method bar = 42
method baz = "hello"
end |
Now we can use the interface as a type, and ensure that we only receive types which implement that interface.
Java: | public void qux (Foo obj )
{
System. out. println(obj. bar());
System. out. println(obj. baz());
} |
code: | let qux (obj : foo) =
print_endline (string_of_int obj#bar);
print_endline obj#baz |
However, OCaml throws us a twist. It's type inferencing nature meanswe can take a shortcut.
code: | let qux (obj : foo) =
print_endline (string_of_int obj#bar);
print_endline obj#baz |
This clearly indicates that "obj" must have bar and baz methods, and that they must return an int and a string respectively. O'Caml knows this at compile-time. As a result, it will reject any object at compile-time which doesn't implement those methods. In place of the above, we could simply write:
code: | let qux obj =
print_endline (string_of_int obj#bar);
print_endline obj#baz |
The interface remains handy because it can prevent us from compiling a class which doesn't implement certain methods.
Next Up
Abstract classes. Stay tuned. |
|
|
|
|
|
wtd
|
Posted: Thu May 12, 2005 1:12 am Post subject: (No subject) |
|
|
Abstract Classes
Let's look at a simple interface for account classes.
code: | class virtual bank_account =
object (self)
method virtual current_balance : int
method virtual deposit : int -> unit
method virtual withdraw : int -> unit
method virtual interest_rate : int
method private virtual pay_interest : unit
end |
Now, let's implement this in a saving account class which provides 3% interest.
code: | class savings_account =
object (self)
inherit bank_account
val mutable balance = 0
method current_balance = balance
method deposit amount =
balance <- balance + amount
method withdraw amount =
self#deposit (-amount)
method interest_rate = 3
method private pay_interest =
self#deposit (int_of_float (float_of_int self#current_balance *. float_of_int self#interest_rate))
end |
But here we can see that there's redundancy. Both the withdraw and pay_interest methods can be defined entirely in terms of other methods. As a result, there's no reason we shouldn't define them in the interface, so that classes which implement "bank_account" won't have to implement those methods.
Fortunately the syntax remains familiar.
code: | class virtual bank_account =
object (self)
method virtual current_balance : int
method virtual deposit : int -> unit
method withdraw amount =
self#deposit (-amount)
method virtual interest_rate : int
method private pay_interest =
self#deposit (int_of_float (float_of_int self#current_balance *. float_of_int self#interest_rate))
end |
And now savings account can be defined as:
code: | class savings_account =
object (self)
inherit bank_account
val mutable balance = 0
method current_balance = balance
method deposit amount =
balance <- balance + amount
method interest_rate = 3
end |
|
|
|
|
|
|
wtd
|
Posted: Fri May 13, 2005 5:58 pm Post subject: (No subject) |
|
|
Recap
We've seen that interfaces and abstract classes in O'Caml are really the same thing, with abstract classes simply providing some default method definitions.
Inheritance
Inheritance? Again? Sure why not...
The fun thing is... interfaces and abstract classes can be extended in O'Caml. Let's look at an example.
code: | class virtual equalable =
object (self : 'a)
method virtual equals : 'a -> bool
method not_equals other =
not (self#equals other)
end |
This abstract class provides for an "equals" method which determines equality between two objects. The "not_equals" method is defined by default.
Due to the nature of O'Caml's type system, we can't actually refer to a class by name within the class. We can, however, use a type signature to specify that "self" is the generic type 'a.
code: | class foo =
object (self)
inherit equalable
val mutable a = 42
method get_a = a
method set_a new_a = a <- new_a
method equals other =
a = other#a
end |
And here we've created a very simple class which implements that abstract class.
Now, what if we want to do a three way comparison. "Less than" returns -1, "equals" returns 0, and "greater than returns 1.
code: | class virtual comparable =
object (self : 'a)
method virtual compare : 'a -> int
end |
Now, I can implement it alongside "equalable" for the same simple class.
code: | class foo =
object (self)
inherit equalable
inherit comparable
val mutable a = 42
method get_a = a
method set_a new_a = a <- new_a
method equals other =
a = other#a
method compare other =
if self#equals other then 0
else if a < other#get_a then -1
else 1
end |
But this is really quite messy. Clearly implementing the comparable interface implies that I've implemented equalable. Fortunately we can directly express that in O'Caml, by having comparable inherit equalable.
code: | class virtual comparable =
object (self : 'a)
inherit equalable
method virtual compare : 'a -> int
end |
My class can then be simplified to:
code: | class foo =
object (self)
inherit comparable
val mutable a = 42
method get_a = a
method set_a new_a = a <- new_a
method equals other =
a = other#a
method compare other =
if self#equals other then 0
else if a < other#get_a then -1
else 1
end |
|
|
|
|
|
|
wtd
|
Posted: Sat May 14, 2005 5:37 pm Post subject: (No subject) |
|
|
Singletons
Those used to Java probably know this ideas better as "anonymous inner classes".
So, can O'Caml do this or something effectively the same?
As of version 3.08 the answer is most definitely yes.
Let's consider a simple Java example. I have an interface for anything that can speak.
Java: | interface Speaker
{
public void speak();
} |
As we've discusses we can replicate this in O'Caml.
code: | class virtual speaker =
object
method virtual speak : unit
end |
Now let's have a method which takes a Speaker object.
Java: | class ObligatoryClass
{
public static void talk(Speaker s)
{
s.speak();
}
} |
And in O'Caml:
code: | let speak (s : speaker) = s#speak |
Now, in Java we can use an anonymous inner class if we don't want to create a class which implements Speaker.
Java: | class ObligatoryClass
{
public static void talk (Speaker s )
{
s. speak();
}
public static void main (String[] args )
{
speak (new Speaker
{
public void speak ()
{
System. out. println("Hello");
}
});
}
} |
And in O'Caml:
code: | speak (object
inherit speaker
method speak = print_endline "hello"
end) |
Consistency
Some might look at O'Caml and see a mish-mash of inconsistency. In places they'd be write. But here there's consistency. An anonymous object is no different from a class.
Type Inferencing
Due to the type system of O'Caml it isn't even really necessary to specify that "s" should be a "speaker". The compiler will see that the "speak" method is called, and only allow the program to compile if the object being passed in implements that method.
Our code could simply be:
code: | let speak s = s#speak;;
speak (object method speak = print_endline "hello" end) |
|
|
|
|
|
|
|
|