Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Objects contrib

5 views
Skip to first unread message

Fabrice Le Fessant

unread,
Jun 16, 1999, 3:00:00 AM6/16/99
to caml...@inria.fr

I have made a small patch to ocaml-2.02 to allow safe casts of objects.
The patch adds two new keywords "implements" and "cast".

- "implements" ("implements c1 c2") is used to declare that objects
from a class c1 can be cast to the type of another class
c2. Implements checks at compile time that a such cast is
safe. Polymorphic classes (class ['a] ...) are not allowed as
"implements" parameters. However, derived classes are allowed
(class b = [int] t). By default, casting objects to their original class
is always allowed without using "implements", but all other casts must be
precedeed by an "implements" which allows them.

- "cast" ("cast o1 c1") is used to cast an object o1 to the type of a class c1.
A runtime check is performed to verify that the original class of the object
can be cast to the new class (thanks to an "implements" instruction). If
not correct, a (Failure "Cast failure") is raised.

Here is an example of use. Such a cast is interesting to retrieve the
original type of an object after it has been subtyped to be stored in
a generic structure.

class t1 () =
object
method a = 0
end;;

class t2 () =
object
method b = 1
method a = 0
end;;

let o1 = new t1 ();;
let o2 = new t2 ();; (* val o2 : t2 *)

let x = (o2 :> t1);; (* val x : t1 *)
let y = cast x t2;; (* cast x to its original class, val y : t2 *)

implements t2 t1;; (* t2 implements the interface of t1 *)
let x = (o2 :> < >);; (* val x: < > no methods *)
let y = cast x t1;; (* cast x to the interface of t1, val y : t1 *)

let z = cast o1 t2;; (* ERROR: Failure "Cast failure" *)
implements t1 t2;; (* ERROR: not (t1 :> t2) *)

This patch is available at:
http://pauillac.inria.fr/~lefessan/src/patch-cast.tar.gz

- Fabrice LE FESSANT

Homepage: http://pauillac.inria.fr/~lefessan


Vyskocil Vladimir

unread,
Jun 16, 1999, 3:00:00 AM6/16/99
to fes...@pa.dec.com
Fabrice Le Fessant wrote:
>
> I have made a small patch to ocaml-2.02 to allow safe casts of objects.
> The patch adds two new keywords "implements" and "cast".

It sound very good to me, I needed such construct and for now I must
"tag"
objects with a sum constructor and "untag" them with pattern matching,
not
very convenient...
Will this patch be integrated in the official ocaml distribution ?

--
Vyskocil Vladimir
vysk...@math.unice.fr
http://pcmath65.unice.fr/~vyskocil
http://www.inria.fr/safir/WHOSWHO/Vladimir.html


Fabrice Le Fessant

unread,
Jun 16, 1999, 3:00:00 AM6/16/99
to caml...@inria.fr

Sorry, the previous link was false.
The patch is available at:
http://pauillac.inria.fr/~lefessan/src/patch.cast.gz

----------------------------------------------------------------------

I have made a small patch to ocaml-2.02 to allow safe casts of objects.
The patch adds two new keywords "implements" and "cast".

- "implements" ("implements c1 c2") is used to declare that objects


from a class c1 can be cast to the type of another class

c2. Implements checks at compile time that such a cast is


- Fabrice

Homepage: http://pauillac.inria.fr/~lefessan


Jacques GARRIGUE

unread,
Jun 17, 1999, 3:00:00 AM6/17/99
to fes...@pa.dec.com
> I have made a small patch to ocaml-2.02 to allow safe casts of objects.
> The patch adds two new keywords "implements" and "cast".

This looks both interesting and dangerous.

Interesting:
Lots of people want such a feature. This is standard in
LISP or JAVA, so why not in Caml?
A slight improvement: I would rather have cast raise Match_failure
than a standard Failure. In particular with a match failure you have
position information for where it failed.

Dangerous:
I believe this really goes against what ML is trying to be.
It allows people to write their programs like they would do in JAVA,
that is in a partially unsafe way. "I believe this object will have
type t, so let's cast it to t." What makes you believe so? I don't see
any guarantee for that.
Another improvement for that would be to use a match construct rather
than only cast, and to have a warning if the match does not contain a
wild card at the end.

Another problem is that it creates a distinction between classes with
parameters and classes without, which is not very natural.

It would have been easy to introduce such a feature in the object
system from the beginning: caml objects bear a pointer to their class,
you just have to put the class name in the class. My guess is that it
was intentionnally left out.

Remark:
You can already implement something similar inside the language:

For each class c create an hash-table referencing the object
(In fact this should not be a standard hash-table, but a set of weak
pointers, otherwise your objects will never be GCed as long as you
don't remove them from the table)

type empty_obj = < >
let memo_c : (empty_obj, c) Hashtbl.t = Hashtbl.create 7

let new_c args =
let obj = new c args in
Hashtbl.add memo_c (obj :> empty_obj) obj;
obj

let cast_c obj = Hashtbl.find memo_c (obj :> empty_obj)

Do not forget to catch Not_found when using cast_c !
This works for parameterized classes also: you just have to create a
different memo for each parameters.
You don't have the flexibility of the implements feature, but you
could also implement it by defining cast_c to look into the memos of
all subclasses in the implement hierarchy.

Regards,

Jacques
---------------------------------------------------------------------------
Jacques Garrigue Kyoto University garrigue at kurims.kyoto-u.ac.jp
<A HREF=http://wwwfun.kurims.kyoto-u.ac.jp/~garrigue/>JG</A>


Fabrice Le Fessant

unread,
Jun 17, 1999, 3:00:00 AM6/17/99
to Jacques GARRIGUE

> A slight improvement: I would rather have cast raise Match_failure
> than a standard Failure. In particular with a match failure you have
> position information for where it failed.

At the end of this message, you will find a patch to apply on
patch.cast to raise a Oo.CastFailure exception with the information
on the file and the line number where the exception was raised.

> Lots of people want such a feature. This is standard in
> LISP or JAVA, so why not in Caml?

> I believe this really goes against what ML is trying to be.

In every language, there is a compromise to do between expressiveness
and static verification of programs (by typing for example). I think
that a great quality of Objective-Caml is that it has reached a very
good compromise, and that each user can decide whether it is most
interested in the safety of the language -- for programing provers or
compilers for example -- or by the ease of programing in this language
-- for fast prototyping, or system programing. In both cases, he will
benefit from all the qualities of the language.

In our case, the "cast" construct for the object-oriented programing
style can be compared to the "ref" or the "array" types for the
imperative programming style. Both can lead to "unsafe" programs
(should the compiler allow "x.(-1)" in a program as it currently does?),
but they can still be useful when used carefully. In that way, I trust
Ocaml users (demagogy :)).

With this patch, I tried to increase the expressiveness of the language,
without breaking its safety. In this way, I think it is still better
than using Obj.magic, since the "implements" checks whether the types
can correctly be coerced.

> Remark:
> You can already implement something similar inside the language:

Can you really use an object (mutable) as the key in the hashtbl (its
hash value may change when it is modified ?) ?

Regards,

- Fabrice

*** tmp/patch.cast Thu Jun 17 09:06:44 1999
--- tmp2/patch.cast Thu Jun 17 09:05:52 1999
***************
*** 4,8 ****
***************
*** 442,447 ****
! --- 442,450 ----
event_after e (Lsend(Lvar met_id, transl_exp expr, []))
| Texp_new (cl, _) ->
--- 4,8 ----
***************
*** 442,447 ****
! --- 442,453 ----
event_after e (Lsend(Lvar met_id, transl_exp expr, []))
| Texp_new (cl, _) ->
***************
*** 10,14 ****
+ | Texp_cast (exp, cl_path, cl_decl) ->
+ Lapply(Translobj.oo_prim "class_cast",
! + [transl_exp exp; transl_path cl_path])
| Texp_instvar(path_self, path) ->
Lprim(Parrayrefu Paddrarray, [transl_path path_self; transl_path path])
--- 10,17 ----
+ | Texp_cast (exp, cl_path, cl_decl) ->
+ Lapply(Translobj.oo_prim "class_cast",
! + [transl_exp exp; transl_path cl_path;
! + Lconst (Const_base(Const_string (
! + Printf.sprintf "File %s: char %d" !Location.input_name
! + e.exp_loc.Location.loc_start)))])
| Texp_instvar(path_self, path) ->
Lprim(Parrayrefu Paddrarray, [transl_path path_self; transl_path path])
***************
*** 257,265 ****
***************
*** 478,480 ****
! --- 487,500 ----
done;
print_newline ())
(sort_buck !bucket_list)
! +
+
+ let class_implements cl1 cl2 =
--- 260,269 ----
***************
*** 478,480 ****
! --- 487,501 ----
done;
print_newline ())
(sort_buck !bucket_list)
! +
! + exception CastFailure of string
+
+ let class_implements cl1 cl2 =
***************
*** 268,275 ****
+ t1.casts <- t2 :: t1.casts
+
! + let class_cast o cl =
+ let t1 = (magic (field (field o 0) 0): table) in
+ let t2 = cl.table in
! + if List.memq t2 t1.casts then o else failwith "Cast failure"
diff -r -C 3 ocaml-2.02/stdlib/oo.mli csl/stdlib/oo.mli
*** ocaml-2.02/stdlib/oo.mli Sun Aug 16 23:28:41 1998
--- 272,279 ----
+ t1.casts <- t2 :: t1.casts
+
! + let class_cast o cl location =
+ let t1 = (magic (field (field o 0) 0): table) in
+ let t2 = cl.table in
! + if List.memq t2 t1.casts then o else raise (CastFailure location)
diff -r -C 3 ocaml-2.02/stdlib/oo.mli csl/stdlib/oo.mli
*** ocaml-2.02/stdlib/oo.mli Sun Aug 16 23:28:41 1998
***************
*** 277,287 ****
***************
*** 68,70 ****
! --- 68,73 ----
distrib : int array; small_bucket_count: int; small_bucket_max: int }
val stats: unit -> stats
val show_buckets: unit -> unit
+
+ val class_implements : class_info -> class_info -> unit
! + val class_cast : Obj.t -> class_info -> Obj.t
diff -r -C 3 ocaml-2.02/typing/typecore.ml csl/typing/typecore.ml
*** ocaml-2.02/typing/typecore.ml Fri Feb 12 13:34:32 1999
--- 281,292 ----
***************
*** 68,70 ****
! --- 68,74 ----
distrib : int array; small_bucket_count: int; small_bucket_max: int }
val stats: unit -> stats
val show_buckets: unit -> unit
+
+ + exception CastFailure of string
+ val class_implements : class_info -> class_info -> unit
! + val class_cast : Obj.t -> class_info -> string -> Obj.t
diff -r -C 3 ocaml-2.02/typing/typecore.ml csl/typing/typecore.ml
*** ocaml-2.02/typing/typecore.ml Fri Feb 12 13:34:32 1999

Stefan Monnier

unread,
Jun 18, 1999, 3:00:00 AM6/18/99
to caml...@inria.fr
>>>>> "Fabrice" == Fabrice Le Fessant <fes...@pa.dec.com> writes:
> At the end of this message, you will find a patch to apply on
> patch.cast to raise a Oo.CastFailure exception with the information
> on the file and the line number where the exception was raised.

I have to agree with Jacques: using a match construct (i.e. `typecase')
rather than a `cast' is cleaner. It makes it a lot more obvious that the
cast might fail and in the case where you want to deal with several
alternatives, a `typecase' is exactly what you want, whereas using `cast'
would be very awkward (using handlers all over the place).
Of course a `cast' is a little better in the case where you *know* that
it will succeed, but the textual overhead of a `typecase' does not seem
significant.

> (should the compiler allow "x.(-1)" in a program as it currently does?),

No it shouldn't. And type-systems are being developed that catch those uses
(pushing array bounds checking either to compile-time or (when the analysis is
not powerful enough) to the programmer (to insert the checks by hand)).


Stefan


Jacques GARRIGUE

unread,
Jun 18, 1999, 3:00:00 AM6/18/99
to fes...@pa.dec.com
From: Fabrice Le Fessant <fes...@pa.dec.com>

> In our case, the "cast" construct for the object-oriented programing
> style can be compared to the "ref" or the "array" types for the
> imperative programming style. Both can lead to "unsafe" programs
> (should the compiler allow "x.(-1)" in a program as it currently does?),
> but they can still be useful when used carefully. In that way, I trust
> Ocaml users (demagogy :)).

Agreed for array indices. Looks like a "needed evil" since the only
way to avoid it would be dependent types (still, some people work at
it).

I don't see what is the problem with refs? This is an impure
feature, but not dangerous in terms of type safety.

And of course there are also exceptions, which are pretty bad when
used the wrong way.

> With this patch, I tried to increase the expressiveness of the language,
> without breaking its safety. In this way, I think it is still better
> than using Obj.magic, since the "implements" checks whether the types
> can correctly be coerced.

I agree that this is better than Obj.magic. However it lets people
program in a JAVA-like style, quitting safe parametric polymorphism
(i.e. parametric classes) for unsafe dynamic type checking.

With your patch one can define collection classes without parameter,
put anything inside them, and downcast to get the contents, like one
does in JAVA.

The problem is that lots of people come to caml from a world where
casts are the standard way to obtain polymorphism. If you provide it
with an easy syntax, they will not bother to learn about the benefits
of parametric polymorphism (anti-demagogy :)).

So I weaken my statement: casts are not an absolute evil which should
not be allowed at all, but at least they should be hard to use, enough
to encourage people to choose other methods when possible.

> > Remark:
> > You can already implement something similar inside the language:
>
> Can you really use an object (mutable) as the key in the hashtbl (its
> hash value may change when it is modified ?) ?

Objects have a unique oid, which is used for comparisons and hashing.
Cf. byterun/hash.c.

I must admit that your approach is more efficient, since you just have
to check the pointer to the class. But again perfection may be a bad
thing for an unsafe feature...

Regards,

Jacques

P.S. Thanks for your path-to-the-patch.


0 new messages