CS 330 Home Schedule Resources

Assignment 4
Kernel Language Semantics and
Automatic Memory Management

Estimated time: 3 hours

The objective of this homework is to understand how a program executes, in particular using tail call optimization.

Exercises

  1. Tail recursion. (20 points) This exercise examines the importance of tail recursion, in the light of the semantics given in the chapter. Consider the following two functions:
    fun {Sum1 N}
       if N==0 then 0 
       else N+{Sum1 N-1} end
    end
    fun {Sum2 N S}
       if N==0 then S else {Sum2 N-1 N+S} end
    end
    
    Now do the following:
    1. Expand the two definitions into kernel syntax. It should be clear that Sum2 is tail recursive and Sum1 is not.
    2. Execute the two calls {Sum1 10} and {Sum2 10 0} by hand, using the semantics of this chapter to follow what happens to the stack and the store. How large does the stack become in either case?

      [As with problem 5 later on: you are welcome to use notational shortcuts, such as showing substitutions instead of environments, using infix + and - operators, and sketching the execution by showing the state of the stack only at key points.

    3. What would happen in the Mozart system if you would call {Sum1 100000000} or {Sum2 100000000 0}? Which one is likely to work? Which one is not? Try both on Mozart to verify your reasoning.Now, try smaller values to pass to Sum, until both versions can execute successfully. How big of a time difference is there in the execution speed?

  2. Expansion into kernel syntax. (20 points) Consider the followingfunction SMergethat merges two sorted lists:
    fun {SMerge Xs Ys}
       case Xs#Ys
       of nil#Ys then Ys
       [] Xs#nil then Xs
       [](X|Xr)#(Y|Yr) then
         if X=< Y then X|{SMerge Xr Ys}
         else Y|{SMerge Xs Yr} end
       end
    end
    

    Expand SMerge into the kernel syntax. Note that X#Yis a tuple of two arguments that can also be written '#'(X Y). The resulting procedure should be tail recursive if the rules of section 2.6.2 are followed correctly.

  3. Mutual recursion. (20 points) Last call optimization is important for much more than jus trecursive calls. Consider the following mutually recursive definition of the functions IsOdd and IsEven:
        fun {IsEven X}
             if X==0 then true else {IsOdd X-1} end
        end
        fun {IsOdd X}
            if X==0 then false else {IsEven X-1} end
        end
    
    We say that these functions are mutually recursive since each function calls the other. Mutual recursion can be generalized to any number of functions. A set of functions is mutually recursive if they can be put in a sequence such that each function calls the next and the last calls the first. For this exercise, show that the calls {IsOdd N} and {IsEven N} execute with constant stack size for all non-negative N. In general, if each function in a mutually recursive set has just one function call in its body, and this function call is a last call, then all functions in the set will execute with their stack size bounded by a constant.

  4. Unification (20 points) Section 2.8.2 explains that the bind operation is actually much more general than just binding variables: it makes two partial values equal (if they are compatible). This operation is called unification. The purpose of this exercise is to explore why unification is interesting. Consider the three unifications X=[a Z] Y=[W b] X=Y. Show that the variables X, Y, Z and W are bound to the same values, no matter in which order the three unifications are done. In chapter 4 we will see that this order-independence is important for declarative concurrency.

  5. Tracing the execution. (20 points) Do ONE of the following exercises. You only need to show the execution state at a few key points. You are also welcome to use the notational shortcuts used in the textbook and/or lecture.

    [Note: the book, as well as these problems, occasionally uses infix operators in kernel syntax, for instance, "T=(X>=Y)" on p. 69. The semantics for any binary operator Z=X+Y are as follows:

    Substitute any binary operator for "+". -Max]
    1. 1 or 2? To familiarize yourself with operational semantics, manually execute the following small program by detailing all the steps. Recall that an instruction is executed in one environment (i.e. a mapping of identifiers to variables), and explain why the second {Browse X} displays the value 1.

         local X in
      X=1
      local X in
      X=2
      {Browse X}
      end
      {Browse X}
      end
    2. Firing squad. Using the formal semantics of the language, execute the two following programs. Pay attention to the order of the instructions in each case!

         % Program 1
      local Res in
      local Arg1 Arg2 in
      Arg1=7 % 1
      Arg2=6 % 2
      Res=Arg1*Arg2 % 3
      end
      {Browse Res}
      end
         % Program 2
      local Res in
      local Arg1 Arg2 in
      Arg1=7 % 1
      Res=Arg1*Arg2 % 3
      Arg2=6 % 2
      end
      {Browse Res}
      end

      What precisely is the final state of each program? What do they each display? What are the variables created by each? What are the values of these variables when the programs finish?

    3. 2 times 3... Consider the program below:

         local
      X=2
      fun {XTimesY Y}
      X*Y
      end
      in
      {Browse {XTimesY 3}}
      end
      Translate this program into the kernel language, then execute it while following the semantic rules. For the translation into the kernel language, do not forget that functions are procedures, and that the calculation of sub-expressions requires the introduction of variables.
    4. Recursion with construction of a list. The function Copy is defined below. This function returns a `` copy '' of the list passed in argument. It goes without saying the function does not have true utility, we make use of it to study its recursive structure.

         fun {Copy Xs}
      case Xs of X|Xr then X|{Copy Xr} else nil end
      end
      Now let us consider two possible translations of this definition of function in the kernel language. In translation 1 on the left, the recursive call { Copy Xr } is carried out before building the list with X. Translation 2 on the right is an alternative. The single difference is the reversed order of the instructions { Copy Xr Yr } and Ys=X | Yr. Explain why these two translations both execute without any problem.

         % Translation 1
      proc {Copy Xs Ys}
      case Xs of X|Xr then
      local Yr in
      {Copy Xr Yr}
      Ys=X|Yr
      end
      else
      Ys=nil
      end

      end
         % Translation 2
      proc {Copy Xs Ys}
      case Xs of X|Xr then
      local Yr in
      Ys=X|Yr
      {Copy Xr Yr}
      end
      else
      Ys=nil
      end

      end

      Let us compare the two alternatives now. Will there be a notable difference between the execution times of the two alternatives? What about memory use? Which alternative does the OZ compiler use? To check, write a small program including the definition of function Copy in the (Mozart) OZ editor, and ask the compiler its translation in the kernel language (menu Oz/Core Syntax).
    5. Minimum of a list. Consider the program below, which defines a function MinLoop. The call {MinLoop X Ys} return the minimum of the elements of the list X|Ys.

         local
      fun {MinLoop X Ys}
      case Ys of Y|Yr then

      if Y<X then {MinLoop Y Yr} else {MinLoop X Yr} end
      else X end

      end
      in
      {Browse {MinLoop 7 [5 8 3]}}
      end
      Translate this program into the kernel language, then execute it while following the rules of semantics. For the translation into the kernel language, do not forget that functions are procedures, and that the calculation of sub-expressions requires the introduction of variables.
    6. With the chain... the function Concat defined below takes in argument a list of lists Ls and returns the concatenation of all these lists. One can see Concat as a generalization of Append for a variable number of lists.

         fun {Concat Ls}
      case Ls of L|Lr then
      {Append L {Concat Lr}}
      else nil end
      end
      This definition, which has the advantage of simplicity, has a weakness however when one calls it with a large list.
      • Highlight this weakness using an example, and explain the reason for this weakness.

      • Bearing in mind the kernel language and the definition of the semantic stack, propose an alternative of Concat not having the problem mentioned.

  6. Feedback.
    Scale
    1. Not at all
    2. Not very
    3. Slightly
    4. Somewhat
    5. Mostly
    6. Very
    7. Extremely