CSCI 421 HW 8
Do Exercise 5 on page 380. Write two ML functions f and g that demonstrate that ML does not implement function calls using macro expansion. Your f should call only g, and your g should make no calls. Explain the results macro expansion would give for your function, and show the results ML actually gives
Selected Answer: Macro expansion substitutes each use of the macro with a complete copy of the macro body, with the actual parameters substituted for the formal parameters. The compiler will only see the transformed version of the statement. If we wanted to demonstrate that ML does not implement function calls using macro expansion, we could write the following two ML functions: fun g () = a; fun f (a) = g (); If ML used macro expansion, the occurrence of a in the body of the function g would be bound to the variable a that is a parameter of the function f. However, with ML, we receive the following error: Error: unbound variable or constructor: a Say a had an external definition: val a = 2; (for example) g( ); then returns val it = 2 : int f(4); 4 is now the value of the parameter a; however, the method call returns val it = 2 : int Even with an external definition of a that allows the function g to compile, the two functions do not do what they're supposed to with macro expansion. Instead, the external definition's value of a is returned instead of f's value of a. Correct Answer: The answer to Exercise 3 is also correct for this exercise. Another approach is to make an example that would work only with capture. In the following example, with macro expansion, the occurrence of a in the body of g would be bound to the variable a that is a parameter of f. Without macro expansion, a is undefined, and the example does not compile. (If a has an external definition that allows g to be compiled, the example still does not do what it would do with macro expansion: it returns the external value of a instead of the value of f's parameter a.) fun g() = a; fun f(a) = g();
Do Exercise 4 on page 380. Make an experiment with the ML language system that demonstrates that ML doesn't recopy lists when they are passed as parameters. Show the results of your experiment and explain what results you would expect if ML recopied list parameters.
Correct Answer: In the following code, the function testcalls(p,i) takes a list of integers p and an integer i, and makes i recursive calls passing p. Each call accesses the head of p (to try to make sure the compiler doesn't optimize the parameter away), and the calls are not tail calls (to try to make sure the compiler actually produces function calls with parameter passing), and the parameter is changed on each call (to try to make sure the compiler does not treat the list as a loop invariant). So if list parameters are copied on calls, testcalls(L,n) for fixed n should take time proportional to the length of L. In fact, by experimenting with testcalls we see that it takes time that is independent of the length of L: whether with val L = repeatedlist(1,1) or with val L = repeatedlist(1,100000), testcalls(L,2000000) takes the same amount of time. (This experiment is good evidence that our ML does not copy lists when passed as parameters. But no such experiment can be perfect when the compiler is treated as a black box. It is just possible, though unlikely, that the compiler is smart enough to notice that testcalls(L,n) = (hd L)*n, and so optimizes the entire execution away!) fun repeatedlist(_,0) = []| repeatedlist(e,i) = e::repeatedlist(e,i-1); fun testcalls(_,0) = 0| testcalls(p,i) = (hd p)+testcalls((hd p)::p,i-1);
Do Exercise 3 on page 380. Make an experiment with the ML language system that demonstrates that ML doesn't pass parameters by name. Show that the results of your experiment, and explain what results by-name parameter passing would have given.
Selected Answer: When passing parameters by name, if a formal parameter is not used in the called method then the corresponding actual parameter is never evaluated. So if we wanted to prove that ML doesn't pass parameters by name then we must prove that ML evaluates all actual parameters whether they are used or not (eager evaluation). We could do so by intentionally including an actual parameter that would result in a divide by zero runtime exception: fun onlyOne (a, b) = b; fun f (x) = onlyOne (1 div 0, x); f(1); Result: uncaught exception Div [divide by zero] This would prove that the actual parameter is evaluated by ML even though the corresponding formal parameter is never used. Correct Answer: When the following f function is evaluated, a divide-by-zero runtime exception occurs. This shows that the actual parameter 1 div 0 passed to g is evaluated, even though f never uses the corresponding formal parameter. By-name parameter passing does not evaluate an actual parameter if the corresponding formal parameter is unused, so that cannot be how ML passes parameters. - fun g(x, y) = y; val g = fn : 'a * 'b -> 'b - fun f(a) = g(1 div 0, a); val f = fn : 'a -> 'a - f(5); uncaught exception Div [divide by zero] raised at: stdIn:2.16-2.19
Do Exercise 6 on page 381. This code fragment uses arrays in Java. The first line declares and allocates an array of two integers. The next two lines initialize it. int [] A = new int[2]; A[0] = 0; A[1] = 2; f(A[0], A[A[0]]); function f is defined as void f(int x, int y){ x = 1; y = 3; } For each of the following parameter-passing methods, say what the final values in the array A would be, after the call of f. (There may be more than on correct answer.) a. by value b. by reference c. by value-result d. by macro expansion e. by name
Selected Answer: a. pass by value: A[0] = 0 and A[1] = 2 b. pass by reference: A[0] = 3 and A[1] = 2 c. pass by value-result: A[0] = 3 and A[1] = 2 OR A[0] = 1 and A[1] = 2 * It depends on the actual order of the parameters in the method call d. pass by macro expansion: A[0] = 1 and A[1] = 3 e. pass by name: A[0] = 1 and A[1] = 3 Correct Answer: A[0] = 0 and A[1] = 2A[0] = 3 and A[1] = 2A[0] = 3 and A[1] = 2, or A[0] = 1 and A[1] = 2, depending on the order in which the value-result parameters are written back after the callA[0] = 1 and A[1] = 3A[0] = 1 and A[1] = 3