34E. Memory Sharing

Memory sharing occurs when two (or more) lists share some list cells between them.

Example: concatenation

The concatenation function cat(A, B) glues two lists A and B together into a single list. For example,

(cat-example.1)   cat([2, 5, 7], [3, 6]) = [2, 5, 7, 3, 6].

Two equations should be obvious.

(cat.1)    cat([ ], B) = B
(cat.2)    cat(A, [ ]) = A

All that is left is the case where A and B are both nonempty. Let's think about that case, and concentrate on how to find the head and the tail of the answer.

  1. The head of cat(A, B) is the same as the head of A. That should be evident from (cat-example.1), where head(A) = 2 and head(cat(A, B)) = 2.

  2. Look again at (cat-example-1). The tail of result [2, 5, 7, 3, 6] is [5, 7, 3, 6], which is equal to cat([5, 7], [3, 6]). That is, the tail of cat(A, B) is cat(tail(A), B).

  3. Remember that h : t is the list whose head is h and whose tail is t. If we know what h and t are, we can build list h : t. Putting the above observations to work,

    (cat.3)    cat(A, B) = head(A) : cat(tail(A), B)

    when A is not empty.

Notice that Equation (cat.3) does not require list B to be nonempty. For example, cat([2, 3, 4], [ ]) = 2 : cat([3, 4], [ ]). Equation (cat.1) tells how to compute cat(A, B) when A is empty and equation (cat.3) tells how to compute cat(A, B) when A is not empty. That covers all possibilities, so there is no need for equation (cat.2). That leads to the following equations for cat.

(cat.1)   cat([ ], B) = B
(cat.3)   cat(A, B) = head(A) : cat(tail(A), B)

Let's do a full hand simulation of cat([2, 4], [6, 8]), even simulating the recursive calls.

   cat([2,4], [6, 8])
   = 2 : cat([4], [6, 8])  by (cat.3)
   = 2 : (4 :cat([ ], [6, 8]))  by (cat.3)
   = 2 : (4 : [6, 8])  by (cat.1)
   = 2 : [4, 6, 8]  since 4 : [6, 8] = [4, 6, 8]
   = [2, 4, 6, 8]  since 2 : [4, 6, 8] = [2, 4, 6, 8]

Converting (cat.1) and (cat.3) to C++ is straightforward, except for one catch, remarked on just after the definition.

  const ListCell* cat(const ListCell* A, const ListCell* B)
  {
    if(A == NULL)
    {
      return B;
    }
    else
    {
      return cons(A->head, cat(A->tail, B));
    }
  }

Memory sharing

Notice that cat(NULL, B) returns B. That means that the list cell that B points to ends up in two different lists: B and the result of cat(A, B). The following illustrates.

As long as you don't change lists, memory sharing does not cause problems, and it can greatly reduce both time and memory utilization. It is not a good idea to combine memory sharing with destructive functions, since that can make it difficult to understand what your program is doing. In the diagram above, if you change 3 to 5 in list B then list C is changed as well, and that can cause confusion.

To share or not to share?

Notice that the return-type of cat is const ListCell*, not ListCell*. Is there anything preventing cat from returning a result of type ListCell*? Yes; B has type const ListCell*, and statement

  return B;
requires the return-type to be the compatible with B. It is not allowed to return a constand pointer and call it a non-constant pointer.

If you want cat to return a result of type ListCell*, there are two approaches. First, you can make B have type ListCell*.

  ListCell* cat(const ListCell* A, ListCell* B)
  {
    if(A == NULL)
    {
      return B;
    }
    else
    {
      return cons(A->head, cat(A->tail, B));
    }
  }

But that means that you cannot pass a const ListCell* value to cat as its second parameter, since doing so would convert a const pointer to a nonconst pointer, which is not allowed. An alternative is to make a copy of B. Assume that copyList takes a const ListCell* L and returns a copy of L, of type ListCell*.

  ListCell* cat(const ListCell* A, const ListCell* B)
  {
    if(A == NULL)
    {
      return copyList(B);
    }
    else
    {
      return cons(A->head, cat(A->tail, B));
    }
  }

But now cat needs to copy list B, which takes extra time and memory. There is no perfect choice.

Exercises

  1. The following equation about cat is false. Give a counterexample that shows it is wrong. Evaluate the two sides for your counterexample and show that they are not equal.

    cat(h : t, u : v) = h : (u : (cat(t, v)))

    Answer

  2. Using equations (cat.1) and (cat.3), show an evaluation of cat([1, 2, 3], [4, 5, 6]) by only replacing expressions by equal expressions. Answer