Chapter 15. Boxed Tuples and Records

A boxed tuple/record is just a reference to some location in memory where a flat tuple/record is stored. Often the word unboxed is also used to describe a flat tuple/record.

In the following code, the types abc_tup and abc_rec are for boxed tuples and records, respectively:

typedef abc_tup = '(int, int, string) // for tuples typedef abc_rec = '{a=int, b=int, c=string} // for records

Note that a tuple is just a special record in the sense that the labels in the tuple are fixed to be ranging from 0 to n-1, inclusive, where n is the tuple length. In the case of abc_tup, the three associated labels are 0, 1, and 2. Note that '( and '{ are special symbols in ATS and there is no space allowed after the single quote. If the use of these special symbols is to be avoided, then one can declare the types abc_tup and abc_rec as follows in a slightly different syntax:

typedef abc_tup = $tup(int, int, string) // for tuples typedef abc_rec = $rec{a=int, b=int, c=string} // for records

Also, the keywords $tup and $rec can be replaced with $tuple and $record, respectively.

The following code demonstrates how tuples and records can be created and their components (i.e., fields) taken out:

// val x_tup = '(0, 1, "2") : abc_tup val x_rec = '{a=0, b=1, c="2"} : abc_rec // val ((*void*)) = assertloc(x_tup.0 = x_rec.a) val ((*void*)) = assertloc(x_tup.1 = x_rec.b) val ((*void*)) = assertloc(x_tup.2 = x_rec.c) //

It should be noted that both x_tup and x_rec are immutable. If one tries to typecheck the following code, then he or she should see some error messages reporting that x_tup.0 and x_rec.a are non-left-values:

// val () = x_tup.0 := 100 // *ERROR*: x_tup.0 not a left-value val () = x_rec.a := 100 // *ERROR*: x_tup.0 not a left-value //

In order to have a tuple/record with mutable fields, we can just literally create a reference to a flat tuple/record. In the following code, the types abc_tup_ and abc_rec_ are declcared for flat tuples and records, respectively:

// typedef abc_tup_ = @(int, int, string) // for tuples typedef abc_rec_ = @{a=int, b=int, c=string} // for records //

and the types abc_tup_r and abc_rec_r are for references to tuples and records classified by abc_tup_ and abc_rec_, respetively:

// typedef abc_tup_r = ref(abc_tup_) // for tuples typedef abc_rec_r = ref(abc_rec_) // for records //

The code below demonstrates how tuples and records with mutable fields can be created and their fields accessed and updated.

// val x_tup_r = ref<abc_tup_>(@(0, 1, "2")) val x_rec_r = ref<abc_rec_>(@{a=0, b=1, c="2"}) // (* ****** ****** *) // val () = assertloc(x_tup_r->0 = x_rec_r->a) val () = assertloc(x_tup_r->1 = x_rec_r->b) val () = assertloc(x_tup_r->2 = x_rec_r->c) // (* ****** ****** *) // val () = x_tup_r->0 := 100 // *OKAY*: x_tup_r.0 is a left-value val () = x_rec_r->a := 100 // *OKAY*: x_rec_r.a is a left-value //

If we want to have records of a type where certain fields are read-only while the others may be updated, we can do this by making use of the support for abstract types in ATS. In the following example, myrec is declared to be abstract; there are three fields associated with myrec that are of the names a, b, and c; the first two may be updated while the third one is read-only:

(* ****** ****** *) abstype myrec = ptr (* ****** ****** *) // extern fun{} myrec_make ( a: int, b: int, c: string ) : myrec // end-of-function // extern fun{} myrec_get_a : myrec -> int extern fun{} myrec_set_a : (myrec, int) -> void extern fun{} myrec_get_b : myrec -> int extern fun{} myrec_set_b : (myrec, int) -> void extern fun{} myrec_get_c : myrec -> string // overload .a with myrec_get_a overload .a with myrec_set_a overload .b with myrec_get_b overload .b with myrec_set_b overload .c with myrec_get_c // (* ****** ****** *) local // assume myrec = abc_rec_r // in (* in-of-local *) // implement {}(*tmp*) myrec_make (a, b, c) = ref(@{a=a, b=b, c=c}) // implement{} myrec_get_a(x) = x->a implement{} myrec_set_a(x, a) = x->a := a // implement{} myrec_get_b(x) = x->b implement{} myrec_set_b(x, b) = x->b := b // implement{} myrec_get_c(x) = x->c (* // // there is no update for the c-field: // implement{} myrec_set_a(x, c) = x->c := c *) // end // end of [local] (* ****** ****** *)

Following is some code that creates a record of the type myrec and then accesses and updates certain fields of the created record:

// val x_abc = myrec_make(0, 1, "2") // val ((*void*)) = assertloc(x_abc.a() = 0) val ((*void*)) = assertloc(x_abc.b() = 1) val ((*void*)) = assertloc(x_abc.c() = "2") // val ((*void*)) = x_abc.a(100) val ((*void*)) = assertloc(x_abc.a() = 100) // val ((*void*)) = x_abc.b(101) val ((*void*)) = assertloc(x_abc.b() = 101) // (* val ((*void*)) = x_abc.c("102") // *ERROR*: unsupported *) //

While this example (myrec) demonstrates an approach to constructing records containing both read-only fields and fields that can be updated, this approach does seem a bit too verbose. One possibilty is to develop meta-programming support so as to greatly reduce the verbosity (due to the need for boilerplate code).

Please find on-line the entirety of the code used in this chapter plus some testing code.