79 Example: Head-Tail List Functions

The Foundations introduction introduces a simple integer list as a recursive value type. Each list cell has a head value and an optional tail holding the next cell. This chapter uses the same shape to show how ordinary functions can be defined over head-tail lists. Head-tail lists are only an example in this chapter; they are not special syntax and these functions are not assumed to be part of the standard library.


value List
{
    Integer head;
    List tail?;
}

This type represents non-empty finite lists. A one-element list has no tail. Longer lists are built by placing another List in the tail.


var xs =
    List {
        head = 1,
        tail = List {
            head = 2,
            tail = List {
                head = 3,
                tail = none
            }
        }
    };

Membership as an Extension

For membership tests, it is useful to expose the integers contained in the list as a derived extension:


value List
{
    Integer head;
    List tail?;

    → Integer member* = head | tail.member;
}

The derived attribute member contains the head value and all values reached through the tail. This is a membership extension, not the list structure itself: it does not preserve order or duplicates. The ordered head-tail structure is still carried by head and tail.

Boolean Membership Function

The Boolean-valued membership function is then a direct membership test against that extension:


func Boolean memberOf(List list, Integer value) =
    value ∈ list.member;

Because the list is the first parameter, the same function can also be called with receiver-call syntax:


memberOf(xs, 2)
xs.memberOf(2)

Both calls ask whether 2 is a member of the list xs. The receiver-call form does not make memberOf a method stored in List; it is still the ordinary function declared above, with xs supplied as its first argument.

Implementation Note

The definition of memberOf does not require an implementation to materialise the whole member extension before testing membership. Since member is defined as head | tail.member, the expression value ∈ list.member is equivalent to testing value = list.head or, when a tail exists, recursively testing value ∈ list.tail.member.


func Boolean memberOf(List list, Integer value)
{
    if list.head = value {
        return true;
    }

    if list.tail = none {
        return false;
    }

    return list.tail.memberOf(value);
}

This block-bodied version is an implementation strategy for the same function. A compiler or query planner may use a demand-driven traversal, memoisation, indexing, or materialisation, provided the observable result is the same as the declarative definition. The second if statement also demonstrates cardinality flow: after the list.tail = none branch returns, the final statement is checked in the case where list.tail is exactly one.