Version française
Home     About     Download     Resources     Contact us    
Browse thread
[Caml-list] examples of heterogenous collections (containers ?)
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: skaller <skaller@u...>
Subject: Re: [Caml-list] examples of heterogenous collections (containers ?)
On Thu, 2004-04-01 at 14:00, briand@aracnet.com wrote:
> >>>>> "skaller" == skaller  <skaller@users.sourceforge.net> writes:
> 
>   skaller> You are thinking about this problem *backwards*! :)
> 
> Well that's why it's giving me so much trouble. :-)

:-)

> I LIKE IT.  No inheritance required. 

you can write (inside a class definition):

	inherit X;

which is just a safe form of 'macro' that pulls in the methods
of X to save you keyboarding. In the OO world this is known
as 'implementation inheritance'.

>   skaller> The solution is to use a factory function in a single
>   skaller> module, and return an abstraction of the class type you
>   skaller> wish to deal with.
> 
> I'm not sure I followed that, can you expand ?

Yeah, I'll explain below ..

> Anyway, enough blather, here's the example :
> 
> class elt = object
>   method f x = 2 * x
> end
> ;;
> 
> class elt_A = object
>   method f x = 3 *x
>   method g x = 3. *. x
> end
> ;;
> 
> let the_list = [ new elt; new elt ; ((new elt_A) :> elt) ]
> ;;

Right, but this example glosses over an important distinction.

In Ocaml .. and in all class based OO languages for that matter ..
a class is NOT a type, its a function which constructs an object.

In OO languages like C++ and Java, there is an associated
type with the same name as the constructor.

In Ocaml, there is ALSO an associated type .. BUT you can make
class types independently. In C++ one uses an 'abstract class'
for this purpose, in Java it is called an interface.

In Ocaml, its called a 'class type'.

For example:

class type elt_t = object
  method f: int -> int
end
;;

Here, elt_t is a class type, notice the declaration is 
for 'class type ...', and that only the signature of the
method f is provided, and no implementation.

When you write:

class elt_A = object method f x = 3 * x end
;;

you are defining two things: a function to make
an object, and also a type alias 'elt_A'.

It is poor practice to use mix up the class type
and the constructor. To ensure it is impossible to do
this consider a factory function:

let make_A () : elt_t =
  class elt_A object method f x = 3 * x end;
  (new elt_A :> elt_t)
;;

The idea is to completely isolate the implementation
of the constructors of objects from their types,
because in Ocaml the two things are separable.

In practice, what you do is:

(1) Put the class type specification in 
the file elt.mli.

(2) In elt_A.mli, you put the signature:

open Elt
val make_A: unit -> elt_A

(3) In elt_A.ml put the class declaration and the
factory function:

open Elt
class elt_A = object method f x = 3 * x end
let make_A () : elt_t =(new elt_A :> elt_t)
;;

Note that the type of the class 'elt_A' is
invisible to the outside world because the
signature is not present in the elt_A.mli
file. All we know is that there is a constructor
named 'make_A' which can make some kind of elt.

The effect of all this is to completely separate the
way you think about the type of object required to
make your algorithms and functions work, from the
details of implementing the objects carrying
the information; that is: you make the algorithms
representation independent, and you can write
and type check them before thinking about
implementing one or more objects.

Note now your example works, but it is confusing
because elt and elt_A are BOTH class types and 
class constructor functions.

In the coercion:

	new elt_A :> elt_t

which I wrote above it is manifest that the
type of the object on the LHS is irrelevant,
except that it is a subtype of elt_t.
All the algorithms work with the abstracted
supertype elt_t: the actual type elt_A of the object
is hidden.

There is a 'golden rule' in OO that your
subtyping lattices should be expressesed independently
of you inheritance lattices.

In Ocaml, you can do this explicitly and should.
In particular, inheritance is clearly just a hack
to save keyboarding: your inheritance lattice
is an implementation detail.

But much more surprising, the subtyping lattice
is IMPLICIT. It is not explicitly based on
names, but entirely based on signatures.
For example:

	class type b = object method f:unit end
	class type d1 = object 
		method f: unit 
		method g: unit 
	end
	class type d2 = object
		method f : unit
		method h : unit
	end
	class type d = object
		method f : unit
		method g : unit
		method h : unit
	end

and now we have

	d1 :> b
	d2 :> b
	d :> b1
	d :> b2
	d :> b

where I use :> to mean 'is subtype of'.

[FEATURE REQUEST: there is no way to assert a subtyping
relation in an interface file. It would be nice to
check this, with something like:

	assert d1 :> b;
]

It is clear that the other kind of 'OO' in C++ and Java
is bogus. It doesn't work properly. Because the typing
is connected to inheritance, the programmer is totally
confused by the distinction between subtyping and specification.

Worse though, subtyping relations are INVASIVE in C++ and Java.
You have to declare a class d is derived from b1
in order to use d 'as a b1'. So when you write
an algorithm which requires a new kind of type
b2, your d which already has the right signature
still cannot be used 'as a b2', you have to go
and invade the definition, breaking the open/closed
principle.

In Ocaml, you don't. If your d already has the right
signature is can be used immediately 'as a b2'.
You can create new abstractions that apply to existing
objects 'after the fact' and it all works.

Once you use a properly constructed system you're
going to hate the idea of going back to using Java
or C++: Ocaml is not perfect, but Java and C++ are just
plain wrong :D

BTW: it is possible to use a dummy method to 'state'
an invariant. For example, there is an intended difference
between

class type pair = object
  method x : int
  method y : int
end

class type gauss = object 
  method x: int 
  method y: int
end

class type rational = object
  method x: int
  method y: int
end

but there is no sugar for stating it:
but we really need to prevent coercing a pair
into a rational!

To solve this problem, you can use a dummy method.
For example to the rational type you add the method:

	method rational_invariant : unit

which you can implement to check the invariant,
or if you're lazy just return (). The method
name is used as a generative tag to prevent
a pair being cast to a rational (but you can
always forget the invariant and cast the rational
to a pair).

I wonder if there is a better way to encode this idea?

-- 
John Skaller, mailto:skaller@users.sf.net
voice: 061-2-9660-0850, 
snail: PO BOX 401 Glebe NSW 2037 Australia
Checkout the Felix programming language http://felix.sf.net



-------------------
To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr
Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/
Beginner's list: http://groups.yahoo.com/group/ocaml_beginners