From: "Tiphaine.Turpin" <Tiphaine.Turpin@free.fr>
To: caml-list@yquem.inria.fr
Subject: Re: [Caml-list] OO programming
Date: Fri, 29 Feb 2008 15:22:50 +0100 [thread overview]
Message-ID: <47C8153A.4080109@free.fr> (raw)
In-Reply-To: <20080225201035.GA5414@feanor>
[-- Attachment #1: Type: text/plain, Size: 11367 bytes --]
Dirk Thierbach a écrit :
> On Mon, Feb 25, 2008 at 10:23:09AM +0100, Tiphaine.Turpin wrote:
>
>> I will try to state at an abstract level what I would like to do :
>>
>> - define parametric virtual classes A and B so that every A has a Bs
>> (one or many...) and vice-versa.
>> - possibly extend A and B to A' and B' respectively, so that every A'
>> has B's (and not just Bs), etc.
>> - subtyping: possibly extend B to B1 and B2 so that their objects have
>> As, but those As still have Bs, so that I can associate the same A to
>> objects of class B, B1 or B2.
>>
>> - and the ultimate goal combining all that: define A and B as above,
>> other virtual classes C and D in a similar way and E and F too, and
>> define concrete classes X Y Z1 Z2 just by saying that X extends A, Y
>> will play the role of B in the first two classes and the role of C in
>> the last two ones, and Z1 Z2 extends D (both) and E and F
>> (respectively). It happens that some of the types that were left
>> parametric (respectively methods defined as virtual) in B are made
>> concrete in C, (and so on), so I obtain my final concrete classes.
>>
>
> If you manage to find a simple type system for that, preferably with
> type inference and principal typings, I'd like to see it :-)
>
I made more extensive tests with my class extension problem, using only
classes, and with a quite acceptable overhead for the programmer (no
modules, no need to write row types by hand). So I'm giving the state of
my understanding in this very long mail. I hope this will be of interest
to the many of you who answered my questions and therefore contributed
to it. The following is about achieving the programming schemes
described above ; don't expect any concrete examples here, as I still
have to try that and see wether it is any usefull for programming.
Actually there are even almost no methods (especially virtual) in what
follows, but I think this doesn't change the rules too much.
First, here is a way to define two classes a and b as above (the kind of
link here is one to one association, but this is not important since the
concern is about typing).
class ['b] a = object
constraint 'b = _ #b
val b : 'b option ref = ref None
method set_b b' = b := Some b'
method get_b = match ! b with
| Some b -> b
| None -> raise Not_found
end
and ['a] b = object
constraint 'a = _ #a
val a : 'a option ref = ref None
method set_a a' = a := Some a'
method get_a = match ! a with
| Some a -> a
| None -> raise Not_found
end
So the two classes are parameterized by the type of the objects they are
associated with. Furthermore this type parameter is constrained to be an
instance of the open type of the other class, as suggested by Remi
Vanicat. This seemed very counter-intuitive to me to give an open type
to the content of a reference (which I expect to be invariant). But it
works with that, and not with the close type (the definition works, but
extension doesn't).
More generally, when defining a set of related classes, each one should
be parameterized by the classes it is direcly linked with.
A very strange thing happens when type-checking this: the constraint 'b
= _ #b is replaced with the stronger constraint 'b = 'b #a #b (and
symetrically in class b). I still don't understand why it happens, but
it is close to what we would like to express. A simpler (stronger)
constraint that one could think of is something like 'self = 'self b a
(which is not allowed) or writing unparameterized recursive classes, but
this is too strong for some usages, because then we cannot associate
objects of type a with objects of a subtype of b. So I view the
constraint 'a = 'a #a #b as meaning a similar thing except that "an
object a may see itself with a stronger type than its associated object
b sees a". So, this definition will allow subtyping of either of the two
classes, or both.
Note that instead of the above choice of parameters, one can use the
whole set of all related classes to parameterize each one. This works
too. However, parameterizing each class just by itself (which should
concentrate all parameters in a single one) leads to very different
results: first, the above "magic" constraint does not appear, even if we
declare (an adapted version of) it explicitely. Is it somewhat implied ?
Anyway, this version produces a typing bug when using the resulting
classes with coercions (bug #00004505). So, for me, the above version
seems to be the best solution at this time.
The above classes work (i.e., type-checks) as expected:
let x = new a
and y = new b
let _ =
x#set_b y;
y#set_a x;
x = x#get_b#get_a && y = y#get_a#get_b
The compatibility of the two classes is checked too at some level, but
not completely. Precisely the methods used on one side and declared on
the other side are checked to be compatible (so that we cannot forget
argumets or things like that) but the actual existence of a used method
is not checked. Conjecture: if any concrete classes declared as above
type-check, then they may be extended in such a way that the connection
of two new objects will type-check). However, there is a way to enforce
the existence of used methods, for example with the following code:
let _check _ : 'b a * ('b a b as 'b) = new a, new b
This is similar to the ckecks suggested by Jacques Garrigue and Julien
Signoles (except that no functors are involved). This works only for
concrete classes, and this is not ideal since this only appears as a
challenge and not in the types of the classes themselves, but this is a
good start. Note that the onstraint 'a = 'self a would work if the two
classes were not recursive. However, the obtained classes could not be
extended. With mutually recursive classes, we get a "Self type cannot
escape its class" error.
Things like the following may also work (but I'm not sure wether it is
equivalent). Extension of a' and b' is not possible then.
class virtual ['b] a' = object (self : 'self)
inherit ['b] a
constraint 'b = 'self b
end
class virtual ['a] b' = object (self : 'self)
inherit ['a] b
constraint 'a = 'self a
end
The following code extends a and b "simultaneously", in the sense that
a2 knows that the object it would get by calling self#get_b is of type
b2, and thus has a compatible method bar.
class ['b] a2 = object
constraint 'b = _ #b2
inherit ['b] a
method foo = ()
end
and ['a] b2 = object
constraint 'a = _ #a2
inherit ['a] b
method bar = ()
end
The constraints are re-stated in terms of the new classes in order to
check the compatibility of new methods. Here is an example of error that
would have been detected:
method bar = self#get_a#foo ()
^^^^^^^^^^^^^^
This expression is not a function, it cannot be applied
We can also repeat the second check:
let _check _ : 'b a2 * ('b a2 b2 as 'b) = new a2, new b2
The two new classes may be used without trouble, as for the first two
ones. Another extension we can do is to make several subclasses of one
of our classes, say b:
class ['a] b' = object
inherit ['a] b
method foo = ()
end
and ['a] b'' = object
inherit ['a] b
method bar = ()
end
This time, we don't add any new constraint to class a, since it must
still accept b objects of class b, as we want to be able to connect
objects of classes both b' and b''. Here is an example:
let x = new a
and y1 = new b'
and y2 = new b''
let _ =
x#set_b (y1 :> _ b);
y1#set_a x;
x#set_b (y2 :> _ b);
y2#set_a x
let _ =
x = x#get_b#get_a && (y2 :> _ b) = y2#get_a#get_b
Of course we don't even need to define classes for that. We could have
used immediate objects. Back to extension, the following is an example
where a and b are the same class:
class ['c] c = object
inherit ['c] b
inherit ['c] a
end
Here is an example of use with subtyping:
let x = object inherit [_] c method foo = () end
and y = object inherit [_] c method bar = () end
let _ =
x#set_b (y :> _ c);
x#set_a (y :> _ c);
y#set_b (x :> _ c);
y#set_a (x :> _ c)
let _ =
(y :> _ c) = x#get_a
&& (y :> _ c) = x#get_b
&& (x :> _ c) = y#get_a
&& (x :> _ c) = y#get_b
let _ =
(x :> _ c) <> (y :> _ c)
Rather that closing the association link on itself we may also combine
two instances of it (this would also work for two differents pairs of
classes):
class ['j] i = object
inherit ['j] a
constraint 'j = (_, _) #j
end
and ['i, 'k] j = object
inherit ['i] b
inherit ['k] a
constraint 'i = _ #i
constraint 'k = _ #k
end
and ['j] k = object
inherit ['j] b
constraint 'j = (_, _) #j
end
let _check _ : 'j i * (('j i, 'j k) j as 'j) * 'j k = new i, new j, new k
This time, the check looks more complex. And finally, the complete
example described at the top:
class ['b] a = object
constraint 'b = 'b #a #b
val b : 'b option ref = ref None
method set_b b' = b := Some b'
method get_b = match ! b with
| Some b -> b
| None -> raise Not_found
end
and virtual ['a] b = object
constraint 'a = 'a #b #a
val a : 'a option ref = ref None
method set_a a' = a := Some a'
method get_a = match ! a with
| Some a -> a
| None -> raise Not_found
method virtual foo : _
end
(* The following cannot be checked, because b is virtual
let _check _ : 'b a * ('b a b as 'b) = new a, new b
*)
class ['d] c = object
constraint 'd = _ #d
val d : 'd option ref = ref None
method set_d d' = d := Some d'
method get_d = match ! d with
| Some d -> d
| None -> raise Not_found
method foo = ()
end
and ['c] d = object
constraint 'c = _ #c
val c : 'c option ref = ref None
method set_c c' = c := Some c'
method get_c = match ! c with
| Some c -> c
| None -> raise Not_found
end
let _check _ : 'd c * ('d c d as 'd) = new c, new d
class ['f] e = object
constraint 'f = _ #f
val f : 'f option ref = ref None
method set_f f' = f := Some f'
method get_f = match ! f with
| Some f -> f
| None -> raise Not_found
end
and ['e] f = object
constraint 'e = _ #e
val e : 'e option ref = ref None
method set_e e' = e := Some e'
method get_e = match ! e with
| Some e -> e
| None -> raise Not_found
end
let _check _ : 'f e * ('f e f as 'f) = new e, new f
class ['y] x = object
inherit ['y] a
constraint 'y = (_, _) #y
end
and ['x, 'z] y = object
inherit ['x] b
inherit ['z] c
constraint 'x = _ #x
constraint 'z = _ #z
end
and ['y] z = object
inherit ['y] d
constraint 'y = (_, _) #y
end
and ['y, 'z2] z1 = object
inherit ['y] z
inherit ['z2] e
end
and ['y, 'z1] z2 = object
inherit ['y] z
inherit ['z1] f
end
let _check _ : 'y x * (('y x, 'y z) y as 'y) * ('y, 'z2) z1 * (('y, ('y,
'z2) z1) z2 as 'z2) = new x, new y, new z1, new z2
let x = new x
and y = new y
and z1 = new z1
and z2 = new z2
let _ =
x#set_b y;
y#set_a x;
y#set_d (z1 :> _ z);
z1#set_c y;
y#set_d (z2 :> _ z);
z2#set_c y;
z1#set_f z2;
z2#set_e z1
Here, the checks are becomming hard to write however. Note that both the
type and implementation of foo were unspecified in b, and this was later
fixed by inheriting from c.
In a real use, each of the successive extensions would probably go in a
dedicated file. I hope that this will allow me to modularize my code
some day :-).
Tiphaine Turpin
[-- Attachment #2: Type: text/html, Size: 13911 bytes --]
next prev parent reply other threads:[~2008-02-29 14:26 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-02-21 9:31 Tiphaine Turpin
2008-02-21 9:42 ` [Caml-list] " Erik de Castro Lopo
2008-02-21 13:38 ` Remi Vanicat
2008-02-24 16:33 ` [Caml-list] " Dirk Thierbach
2008-02-25 9:23 ` Tiphaine.Turpin
2008-02-25 15:48 ` Edgar Friendly
2008-02-25 16:02 ` Berke Durak
2008-02-25 20:12 ` Dirk Thierbach
2008-02-25 20:51 ` Tiphaine.Turpin
2008-02-25 23:03 ` Dirk Thierbach
2008-02-25 20:10 ` Dirk Thierbach
2008-02-25 21:49 ` Tiphaine.Turpin
2008-02-25 23:07 ` Dirk Thierbach
2008-02-29 14:22 ` Tiphaine.Turpin [this message]
2008-02-26 6:17 ` Jacques Garrigue
2008-02-26 9:36 ` Julien Signoles
2008-02-27 0:25 ` Tiphaine.Turpin
2008-02-27 1:37 ` Jacques Garrigue
2008-02-28 8:34 ` Keiko Nakata
2008-02-28 13:30 ` Andrej Bauer
2008-02-28 15:18 ` Keiko Nakata
2008-02-28 16:02 ` Edgar Friendly
2008-02-29 14:35 ` Tiphaine.Turpin
2008-02-29 15:58 ` Keiko Nakata
2008-03-03 9:40 ` Tiphaine.Turpin
2008-03-03 10:20 ` Jacques Garrigue
2008-03-03 10:30 ` Tiphaine.Turpin
2008-03-03 15:18 ` Keiko Nakata
2008-03-03 19:25 ` Tiphaine Turpin
2008-03-04 14:00 ` Keiko Nakata
2008-02-27 7:40 ` Dirk Thierbach
2008-02-27 14:04 ` Tiphaine.Turpin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=47C8153A.4080109@free.fr \
--to=tiphaine.turpin@free.fr \
--cc=caml-list@yquem.inria.fr \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox