caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] examples of heterogenous collections (containers ?)
@ 2004-03-31  4:55 briand
  2004-03-31  5:41 ` Matt Gushee
  0 siblings, 1 reply; 8+ messages in thread
From: briand @ 2004-03-31  4:55 UTC (permalink / raw)
  To: caml-list


I'm embarking on that most typical of ewexamples, the heterogenous list.

So I have objects:

base_class
  virtual method

derived_class_A
  inherit base_class

derived_class_B
  inherit base_class

...

And I would like to collect instances of derived_class_B,
derived_class_B etc.. into a data structure.

The obvious example is that I'd like to collect the objects into a
list and do something like :

List.iter 
  (f obj ->
    obj#method)
  list_of_objs

or is (obj :> base_class)#method ?


I've done quite a bit of searching but I cannot seem to find a "clean"
example of doing the above, just bits and pieces.  Can anyone point me
to any examples or code  ?

Thanks


Brian

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] examples of heterogenous collections (containers ?)
  2004-03-31  4:55 [Caml-list] examples of heterogenous collections (containers ?) briand
@ 2004-03-31  5:41 ` Matt Gushee
  2004-03-31  6:45   ` briand
  2004-03-31  7:28   ` Martin Jambon
  0 siblings, 2 replies; 8+ messages in thread
From: Matt Gushee @ 2004-03-31  5:41 UTC (permalink / raw)
  To: caml-list

On Tue, Mar 30, 2004 at 08:55:36PM -0800, briand@aracnet.com wrote:
> 
> I'm embarking on that most typical of ewexamples, the heterogenous list.
> 
> So I have objects:
> 
> base_class
>   virtual method
> 
> derived_class_A
>   inherit base_class
> 
> derived_class_B
>   inherit base_class
> 
> ...
> 
> And I would like to collect instances of derived_class_B,
> derived_class_B etc.. into a data structure.
> 
> The obvious example is that I'd like to collect the objects into a
> list and do something like :
> 
> List.iter 
>   (f obj ->
>     obj#method)
>   list_of_objs

This will work if all the classes have identical signatures. Of course,
that's not the case for most non-trivial examples.

> or is (obj :> base_class)#method ?

Probably not. First of all, you will need to cast the objects at the
point where you put them into a list together ... thus, at the point
where you do the method call, the cast has already been done. Second,
you don't necessarily want to cast them to the base class.

As some people never tire of pointing out ;-), inheritance is not 
subtyping. Actually, I haven't myself figured out all the implications
of that statement, nor learned the theoretical justification for it--but
at any rate, in OCaml class types are completely independent of
inheritance relationships. That's a good thing because it means you can
define a class type and arbitrarily apply it to any class that provides
*at least* the methods specified in the type signature, regardless of
inheritance. E.g.:

  class type x =
    object
      method a : unit
      method b : int -> bool
    end

... and so on.

> I've done quite a bit of searching but I cannot seem to find a "clean"
> example of doing the above, just bits and pieces.  Can anyone point me
> to any examples or code  ?

Hmm ... I recently wrote a text editor that uses a lot of class casts in
order to be able to manipulate groups of GUI objects that are instances
of different classes. I'm not sure it's a great learning example,
though. It works, but its code is hardly clean ... in fact it's easily
the ugliest thing I've done in OCaml. But if you can't find any better
examples, I could send you a tarball of it (it's not released yet).

-- 
Matt Gushee                 When a nation follows the Way,
Englewood, Colorado, USA    Horses bear manure through
mgushee@havenrock.com           its fields;
http://www.havenrock.com/   When a nation ignores the Way,
                            Horses bear soldiers through
                                its streets.
                                
                            --Lao Tzu (Peter Merel, trans.)

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] examples of heterogenous collections (containers ?)
  2004-03-31  5:41 ` Matt Gushee
@ 2004-03-31  6:45   ` briand
  2004-03-31  9:04     ` skaller
  2004-03-31  7:28   ` Martin Jambon
  1 sibling, 1 reply; 8+ messages in thread
From: briand @ 2004-03-31  6:45 UTC (permalink / raw)
  To: Matt Gushee; +Cc: caml-list

>>>>> "Matt" == Matt Gushee <mgushee@havenrock.com> writes:

  Matt> This will work if all the classes have identical signatures. Of course,
  Matt> that's not the case for most non-trivial examples.

The classes have identical signatures, i.e. all have exactly the same
methods and variables ??


  Matt> Probably not. First of all, you will need to cast the objects at the
  Matt> point where you put them into a list together ... thus, at the point
  Matt> where you do the method call, the cast has already been done. Second,
  Matt> you don't necessarily want to cast them to the base class.

Well that makes sense, if I can figure out the part about all the
classes havin g the same signature.

  Matt> As some people never tire of pointing out ;-), inheritance is
  Matt> not subtyping. Actually, I haven't myself figured out all the
  Matt> implications of that statement, nor learned the theoretical

Yes, I've been seeing that, and amazingly it's actually starting to
sink in.  There are some notes from the ocaml page which discuss this
relatively well : Didier Remy's APPSEM course notes on Objective Caml.

  Matt> justification for it--but at any rate, in OCaml class types
  Matt> are completely independent of inheritance
  Matt> relationships. That's a good thing because it means you can
  Matt> define a class type and arbitrarily apply it to any class that
  Matt> provides *at least* the methods specified in the type
  Matt> signature, regardless of inheritance. E.g.:

  Matt>   class type x =
  Matt>     object
  Matt>       method a : unit
  Matt>       method b : int -> bool
  Matt>     end

  Matt> ... and so on.

??? So it's example time :

class virtual base =
  ...
end

class A1 = object(self)
inherit base
method x1 = ...
method x2 = ...
end

class A2 = object(self)
inherit base
method x1 = ...
method x2 = ...
method x3 = ...
end

So I should be able to do :

let a = new A1 in
let b = new A2 in
  (a :> base) :: (b :> base) :: []
;;


right ?  now my list has type "base list".

So I immediately see the problem here.

My iter code could try to invoke method x3 on the A1 object in the
list as I'm iterating through. oops.

So it seems to me that I need a "stronger" method to enforce safety,
perhaps implementing x1 as a virtual method in base ?

Brian

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] examples of heterogenous collections (containers ?)
  2004-03-31  5:41 ` Matt Gushee
  2004-03-31  6:45   ` briand
@ 2004-03-31  7:28   ` Martin Jambon
  1 sibling, 0 replies; 8+ messages in thread
From: Martin Jambon @ 2004-03-31  7:28 UTC (permalink / raw)
  To: Matt Gushee; +Cc: caml-list

On Tue, 30 Mar 2004, Matt Gushee wrote:

> On Tue, Mar 30, 2004 at 08:55:36PM -0800, briand@aracnet.com wrote:
> >
> > I'm embarking on that most typical of ewexamples, the heterogenous list.
> >
> > So I have objects:
> >
> > base_class
> >   virtual method
> >
> > derived_class_A
> >   inherit base_class
> >
> > derived_class_B
> >   inherit base_class
> >
> > ...
> >
> > And I would like to collect instances of derived_class_B,
> > derived_class_B etc.. into a data structure.
> >
> > The obvious example is that I'd like to collect the objects into a
> > list and do something like :
> >
> > List.iter
> >   (f obj ->
> >     obj#method)
> >   list_of_objs
>
> This will work if all the classes have identical signatures. Of course,
> that's not the case for most non-trivial examples.
>
> > or is (obj :> base_class)#method ?
>
> Probably not. First of all, you will need to cast the objects at the
> point where you put them into a list together ... thus, at the point
> where you do the method call, the cast has already been done.

And to keep trace of the original object, we would use polymorphic
variants:

class virtual ['a] base_class =
object
  method virtual variant : 'a
end

class ['a] derived_class_A =
object (self)
  inherit ['a] base_class
  method variant = `A self
end

and ['a] derived_class_B =
object (self)
  inherit ['a] base_class
  method variant = `B self
end

type variant = [ `A of variant derived_class_A
	       | `B of variant derived_class_B ]

let a = new derived_class_A
let b = new derived_class_B
let l = [ (a :> variant base_class);
	  (b :> variant base_class) ]
let list_of_variants = List.map (fun o -> o#variant) l


But maybe it is more convenient to not define a variant method, just
build a list of specialized objects:
  let list_of_variants = [ `A obj1; `B obj2; `C anything; ... ]

and then:
  let l = List.map (function `A o | `B o | `C o -> (o :> base_class))

(but here you loose the reference to the specialized version)


Martin

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] examples of heterogenous collections (containers ?)
  2004-03-31  6:45   ` briand
@ 2004-03-31  9:04     ` skaller
  2004-04-01  4:00       ` briand
  0 siblings, 1 reply; 8+ messages in thread
From: skaller @ 2004-03-31  9:04 UTC (permalink / raw)
  To: briand; +Cc: Matt Gushee, caml-list

On Wed, 2004-03-31 at 16:45, briand@aracnet.com wrote:

> So it seems to me that I need a "stronger" method to enforce safety,
> perhaps implementing x1 as a virtual method in base ?

You are thinking about this problem *backwards*! :)

Forget about how to declare the classes etc.

Focus on the list. You would like a list of some
type "elt" for which you can write:

	List.iter (fun x -> x#f) the_list;

Now define the type which 'the_list' must have.
Define the 'minimal' such type!

class type elt = object
	method f : unit
end

val the_list : elt list

That's it. All you need to do now is cast ANY object
to type "elt" and it will fit into your list.

The cast will always work if the object has a method
named 'f' which when invoked with no arguments returns unit.
Obviously, because Ocaml is wonderful, your cast will fail
on objects without a suitable 'f' method.

The KEY idea here is: the class types you need
for algorithms should be declared.

But forget the idea classes have types. They do,
but it isn't important. A class is just a function
which creates an object. Class types are *views*
of objects.

The class type of a class is the 'maximal' view
of an object which it constructs, and isn't 
really that important, in fact, it should probably
never be exposed in any interfaces because the full
class type will include data members due to
a problem needing to allocate space for them
when constructing an object.

The solution is to use a factory function in
a single module, and return an abstraction
of the class type you wish to deal with.


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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] examples of heterogenous collections (containers ?)
  2004-03-31  9:04     ` skaller
@ 2004-04-01  4:00       ` briand
  2004-04-01  5:38         ` Issac Trotts
  2004-04-01  7:20         ` skaller
  0 siblings, 2 replies; 8+ messages in thread
From: briand @ 2004-04-01  4:00 UTC (permalink / raw)
  To: skaller; +Cc: Matt Gushee, caml-list

>>>>> "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. :-)

  skaller> The cast will always work if the object has a method named
  skaller> 'f' which when invoked with no arguments returns unit.
  skaller> Obviously, because Ocaml is wonderful, your cast will fail
  skaller> on objects without a suitable 'f' method.

I LIKE IT.  No inheritance required.  Objects which have the same
method with the same signature work "automagically".

  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 ?

For future readers of the list here and appropriate example.  It turns
out I was silly and never even bothered to look in the _manual_ of all
places.  I looked everywhere else.  This is covered in the section
called "Using Coercions", go figure.

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) ]
;;

List.iter (fun x -> print_int (x#f 2);) the_list
;;

It does the right thing.  Here is a nice example to show type safety.
Re-define elt_A as:

class elt_A = object
  method f x = 3. *. x
  method g x = 3. *. x
end

And get the error :

This expression cannot be coerced to type elt = < f : int -> int >;
it has type elt_A = < f : float -> float; g : float -> float >
but is here used with type #elt as 'a = < f : int -> int; .. >


Thanks to all who answered.


Brian

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] examples of heterogenous collections (containers ?)
  2004-04-01  4:00       ` briand
@ 2004-04-01  5:38         ` Issac Trotts
  2004-04-01  7:20         ` skaller
  1 sibling, 0 replies; 8+ messages in thread
From: Issac Trotts @ 2004-04-01  5:38 UTC (permalink / raw)
  To: caml-list

On Wed, Mar 31, 2004 at 08:00:07PM -0800, briand@aracnet.com wrote:
> let the_list = [ new elt; new elt ; ((new elt_A) :> elt) ]

I wish I understood why this cast is necessary.  It seems like OCaml
should be able to construct a class type whose method signatures 
are the ones common to all objects in the list.  

--
Issac Trotts

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] examples of heterogenous collections (containers ?)
  2004-04-01  4:00       ` briand
  2004-04-01  5:38         ` Issac Trotts
@ 2004-04-01  7:20         ` skaller
  1 sibling, 0 replies; 8+ messages in thread
From: skaller @ 2004-04-01  7:20 UTC (permalink / raw)
  To: briand; +Cc: skaller, Matt Gushee, caml-list

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2004-04-01 12:25 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2004-03-31  4:55 [Caml-list] examples of heterogenous collections (containers ?) briand
2004-03-31  5:41 ` Matt Gushee
2004-03-31  6:45   ` briand
2004-03-31  9:04     ` skaller
2004-04-01  4:00       ` briand
2004-04-01  5:38         ` Issac Trotts
2004-04-01  7:20         ` skaller
2004-03-31  7:28   ` Martin Jambon

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).