Le 24/03/2012 19:26, Goswin von Brederlow a écrit :
Hi,
consider the code below that counts how often a value is printed.
The reason why this code works is that the memory layout of a
variant with arguments is identical to a record with the same
types. The only difference is that a variant sets the tag of the memory
block to reflect which constructor it is (here Bar = 0, Baz = 1).
But the code is fragile. It requires the use of Obj.magic and duplicates
the definitions of bar and baz (once as variant and once as record). If
the type of foo is changed but not the records then bad things will
happen.
There are ways to do this without Obj.magic:
type foo = Bar of bar_record | Baz of baz_record
type foo = Bar of int * int ref | Baz of float * int ref
The first adds an indirection for every access to foo and breaks
matching. The second adds an indirection for every mutable value in the
type. In both cases the extra indirections increase the memory footprint
and runtime.
So why not allow mutable in variant types and some equivalence with
records? For example:
type<name> =<Constructor> of [mutable]<type>[:label] [| ...]
as in:
type foo =
| Bar of int:i * mutable int:bar_used
| Baz of float:f * mutable int:baz_used
let use x =
match x with
| Bar bar ->
bar.bar_used<- bar.bar_used + 1
| Baz baz ->
baz.baz_used<- baz.baz_used + 1
let print x =
use x;
match x with
| Bar bar -> Printf.printf "%d\n" bar.i
| Baz baz -> Printf.printf "%f\n" baz.f
The label is optional and any types in the constructor without label
would be translated into anonymous fields in a record that are
ineaccessible.
type foo = Foo of int * mutable int:used
would be equivalent to { _ : int; mutable used : int; }
Taking it one step wurther one could even allow:
let bar = { i = 1; bar_used = 0; }
let foo = Bar bar
let foo = let Bar bar = foo in Bar { bar with i = 2; }
What do you think?
MfG
Goswin
======================================================================
module Foo : sig
type foo
val make_bar : int -> foo
val make_baz : float -> foo
val get_used : foo -> int
val print : foo -> unit
end = struct
type foo = Bar of int * int | Baz of float * int
type bar_record = { i : int; mutable bar_used : int; }
type baz_record = { f : float; mutable baz_used : int; }
let make_bar i = Bar (i, 0)
let make_baz f = Baz (f, 0)
let use x =
match x with
| Bar _ ->
let (bar : bar_record) = Obj.magic x
in bar.bar_used<- bar.bar_used + 1
| Baz _ ->
let (baz : baz_record) = Obj.magic x
in baz.baz_used<- baz.baz_used + 1
let get_used = function Bar (_, used) | Baz (_, used) -> used
let print x =
use x;
match x with
| Bar (i, _) -> Printf.printf "%d\n" i
| Baz (f, _) -> Printf.printf "%f\n" f
end;;
let foo = Foo.make_bar 1
let used_before = Foo.get_used foo
let () = Foo.print foo
let used_after = Foo.get_used foo
Hello,
I have been wishing for this for a long time. Mainly because it's just
tedious to declare a record for each constructor. Too often do I start
with constructors with a low argument count (say 1 to 3) and find that I
have to add new arguments. At some point I have so many arguments that I
really have to declare the record, and thus rewrite all the relevant
parts of the code.
I would add that it would be convenient for me to be able to name only
*some* of the arguments. For instance :
type t = Sum of pos: Lexing.position * t * t
I would access the anonymous arguments using pattern-matching as usual,
and use ".pos" as a shortcut sometimes.
Unifying records and sums is great, unifying tuples at the same time
seems even better to me. The OPA language (of Mlstate) does this, if I'm
not mistaken.
Cheers,
--
Romain Bardou
--
Caml-list mailing list. Subscription management and archives:
https://sympa-roc.inria.fr/wws/info/caml-list
Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
Bug reports: http://caml.inria.fr/bin/caml-bugs