27B. 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.

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

Memory sharing

Notice that cat(NULL, B) returns B. But really, a List is a pointer to a ListCell. That means that pointer B 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 combin 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 ConstList, not List. Is there anything preventing cat from returning a result of type List? Yes; B has type ConstList, and statement

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

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

  List cat(ConstList A, List B)
  {
    if(A == NULL)
    {
      return B;
    }
    else
    {
      return cons(A->head, cat(A->tail, B));
    }
  }

But that means that you cannot pass a ConstList 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 ConstList L and returns a copy of L, of type List.

  List cat(ConstList A, ConstList 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