Heap-Allocated Linear Closure-Functions

In ATS, a closure-function can be assiged a linear type, allowing it to be properly tracked within the type system and also explicitly freed by the programmer.

The following code implements a higher-order function list_map_cloptr which takes a linear closure-function as its second argument:

fun{
a:t@ype}{b:vt@ype
} list_map_cloptr{n:int}
(
  xs: list (a, n), f: !(a) -<cloptr1> b
) : list_vt (b, n) =
(
  case+ xs of
  | list_nil () => list_vt_nil ()
  | list_cons (x, xs) => list_vt_cons (f (x), list_map_cloptr<a><b> (xs, f))
)

Note that the keyword -<cloptr1> indicates that the function type it forms is for a linear closure-function. If a type for a pure linear closure-function is needed, the keyword -<cloptr0> can be used. The symbol ! in front of the function type means that the second (linear) argument of list_map_cloptr is call-by-value and it is still available after list_map_cloptr returns.

Let us now see some concrete code in which a linear closure-function is created, called, and finally freed:

implement
main0 () =
{
//
val xs =
$list_vt{int}(0, 1, 2, 3, 4)
//
val len = list_vt_length (xs)
//
val f = lam (x: int): int =<cloptr1> x * len
//
val ys =
list_map_cloptr<int><int> ($UNSAFE.list_vt2t(xs), f)
//
val () = cloptr_free($UNSAFE.castvwtp0{cloptr(void)}(f))
//
val () = println! ("xs = ", xs) // xs = 0, 1, 2, 3, 4
val () = println! ("ys = ", ys) // ys = 0, 5, 10, 15, 20
//
val ((*freed*)) = list_vt_free (xs)
val ((*freed*)) = list_vt_free (ys)
//
} (* end of [main0] *)

The function cloptr_free is given the following interface:

fun cloptr_free{a:t0p}(pclo: cloptr (a)):<!wrt> void

Also, the cast involved in $UNSAFE.castvwtp0{cloptr(void)}(f) is a safe cast.

The support for linear closure-functions in ATS1 is crucial in a setting where higher-order functions are needed but run-time garbage collection (GC) is not allowed or supported. In ATS2, linear closure-functions become much less important as programming with higher-order functions in a setting without GC can be more conveniently achieved through the use of templates. However, if one wants to store closure-functions in a data structure without causing memory leaks, it is necessary to use linear closure-functions unless GC can be relied upon to reclaim memory.