Programming C, C++, Java, PHP, Ruby, Turing, VB
Computer Science Canada 
Programming C, C++, Java, PHP, Ruby, Turing, VB  

Username:   Password: 
 RegisterRegister   
 O'Caml for OO Programmers
Index -> Programming, General Programming -> Functional Programming
View previous topic Printable versionDownload TopicSubscribe to this topicPrivate MessagesRefresh page View next topic
Author Message
wtd




PostPosted: 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
Sponsor
sponsor
wtd




PostPosted: 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




PostPosted: 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. Smile
wtd




PostPosted: 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




PostPosted: 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




PostPosted: 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)
Display posts from previous:   
   Index -> Programming, General Programming -> Functional Programming
View previous topic Tell A FriendPrintable versionDownload TopicSubscribe to this topicPrivate MessagesRefresh page View next topic

Page 1 of 1  [ 6 Posts ]
Jump to:   


Style:  
Search: