Elements of ML Programming, 2nd Edition (ML97)

Solutions for Chapter 3

Solutions for Section 3.1

Solutions for Section 3.2

Solutions for Section 3.3

Solutions for Section 3.4

Solutions for Section 3.5

Solutions for Section 3.6

Solutions for Section 3.1

Revised 3/17/05.

Exercise 3.1.1(a)

     fun cube(x:real) = x*x*x;

Exercise 3.1.1(c)

     fun third(L) = hd(tl(tl(L)));

Exercise 3.1.1(e)

     fun thirdChar(s) = third(explode(s));

Exercise 3.1.2(a)

     fun minMax(a,b,c) =
         if a<b then
             if b<c then (a,c)
             else (* b is largest *)
                 if a<c then (a,b) else (c,b)
         else (* a >= b *)
             if a<c then (b,c)
             else (* a is largest *)
                 if b<c then (b,a) else (c,a);

Exercise 3.1.3(a)

f multiplies its argument by 2, so the result is 8.

Exercise 3.1.3(b)

The value of f(4) is still 8, and the previous definition of b was 3, so the result is 11. Note that the b referred to in the expression f(4)+b cannot be the b that is the parameter of f, because the latter is only accessible within the definition of f.

Exercise 3.1.3(e)

g(6) = 6+3, so we must compute f(9), or 18.

Return to Top

Solutions for Section 3.2

Exercise 3.2.1(a)

     fun fact(n) =
             if n<=1 then 1 else n*fact(n-1);

Exercise 3.2.1(c)

     fun duplicate(L) =
             if L=nil then nil else hd(L)::hd(L)::duplicate(tl(L));

Exercise 3.2.1(f)

Note: Because real is not an equality type, and the function null to test whether a list is empty even if the list is not of an equality type is not introduced in Chapter 3, I have changed Exercise 3.2.1(f) to ask for the maximum of a list of strings instead, and the code below reflects that change. If you want to make the code work for lists of reals, you have to replace tl(L)=nil in the second line by null(tl(L)), and of course change the type declaration on line (1).
fun maxList(L: string list) =
	if tl(L)=nil (* L is a single element *)
		then hd(L) (* the single element is the maximum *)
		else (* assume there are at least 2 elements *)
			if hd(L)>hd(tl(L)) (* the first element
					exceeds the second *)
				then maxList(hd(L)::tl(tl(L))) (* eliminate
						second element *)
				else maxList(tl(L)) (* eliminate first
						element *);
Download this program

There are considerably simpler ways to solve this problem if we use the let-construct described in Section 3.4.

Exercise 3.2.2

In the second line we have expression c+1. Since 1 is an integer, c must also be an integer. In the third line, the expressions following the then and else must be of the same type. One of these is c, so the type of both is integer. Thus, expression b+d is of integer type. Therefore b and d must also be integers. Finally, since a and b are compared on lines 2 and 3, they must be of the same type. Hence a is also an integer.

Exercise 3.2.3(a)

Since a is compared with b+c, the latter sum must be an integer. Therefore both b and c must be integers. We cannot tell what types d and e have, although they must be of the same type.

Exercise 3.2.3(c)

Since a is compared with b, b must be an integer. Therefore, the sum b+c is an integer, and so is c. Also, d+e must have the same type as b+c, so both d and e are integers as well.

Exercise 3.2.2(f)

Since there is no way to infer a type for b or c without using the default rules, the default applies to operator <. Thus, b and c are both inferred to be integers. We still cannot infer types for d or e. However, since they are each the result in one branch of the if-then-else, their types must be the same.

Return to Top

Solutions for Section 3.3

Exercise 3.3.1(a)

     fun fact(1) = 1
     |   fact(n) = n*fact(n-1);

Exercise 3.3.1(d)

     fun duplicate(nil) = nil
     |   duplicate(x::xs) = x::x::duplicate(xs);

Exercise 3.3.1(f)

     fun maxList([x:real]) = x
     |   maxList(x::y::zs) =
             if x<y then maxList(y::zs)
             else maxList(x::zs);

Exercise 3.3.5(a)

Yes; x="a", y="b", zs=["c"], and w=["d","e"].

Exercise 3.3.5(c)

No; the expression y::zs in the pattern is forced to match nil in the expression. We fail, since constant symbols :: and nil cannot be matched. Put another way, the first component of the pair has to be a list of length at least 2 in order to match the pattern x::y::zs.

Exercise 3.3.7

     fun square(0) = 0
     |   square(n) = square(n-1)+2*n-1;

Exercise 3.3.8

     fun flip(nil) = nil
     |   flip((x as (a:int,b))::xs) =
             if a<b then x::flip(xs) else (b,a)::flip(xs);

Exercise 3.3.11(a)

We first check to see whether the set S is empty, that is, whether the list is nil. If so, x is not in S. If not, then we compare x with the head of the list. If the head equals x, then x is in S. Otherwise, we recursively test whether x is in the tail of the list.
     fun member(_,nil) = false
     |   member(x,y::ys) =
             (x=y orelse member(x,ys));
We might be tempted to replace the second line by
     |   member(x,x::xs) = true
     |   member(x,_::xs) = member(x,xs);
However, it is not permitted to use an identifier like x twice in one pattern.

Exercise 3.3.11(c)

     fun insert(x,nil) = [x]
     |   insert(x,S as y::ys) =
             if x=y then S else y::insert(x,ys);

Exercise 3.3.12

     fun insertAll(a,nil) = nil
     |   insertAll(a,L::Ls) = (a::L)::insertAll(a,Ls);
The idea behind the second line is that we put a on the front of the first list, and then in the recursive call put a on the front of the remaining lists.

Exercise 3.3.13

     fun powerSet(nil) = [nil]
     |   powerSet(x::xs) =
             powerSet(xs)@insertAll(x,powerSet(xs));
The function above computes the power set as follows. For a basis, the power set of the empty set is the set containing the empty set. The latter is represented by the list [nil], as in the first line of function powerSet above. If the given list is not empty, we take the first element x, compute the power set of the remaining elements, and concatenate this list with the list of all sets with x added. Thus, all the sets that have x and those that do not are included in the overall list. Incidentally, there is a better way to write this function that does not require us to compute powerSet(xs) twice. However, we do not discuss the needed construct, the ``let''-expression, until Section 3.4.

Exercise 3.3.14

     fun prodDiff1(_,nil) = 1.0
     |   prodDiff1(a,b::bs) = (a-b)*prodDiff1(a,bs);

     fun prodDiff(nil) = 1.0
     |   prodDiff(b::bs) = prodDiff1(b,bs)*prodDiff(bs);

Exercise 3.3.15

     fun emptyList(nil) = true
     |   emptyList(_) = false;

Return to Top

Solutions for Section 3.4

Exercise 3.4.1

The function below is about as succinct as can be managed.
     fun thousandthPower(x:real) =
             let
                 val x = x*x*x*x*x;
                 val x = x*x*x*x*x;
                 val x = x*x*x*x*x
             in
                 x*x*x*x*x*x*x*x
             end;
Download this program

Exercise 3.4.3

The following code uses insertAll from Exercise 3.3.12.
     fun powerSet(nil) = [nil]
     |   powerSet(x::xs) =
             let
                 val L = powerSet(xs)
             in
                 L @ insertAll(x,L)
             end;
Download the complete program

Exercise 3.4.5

     fun doubleExp(x:real,0) = x
     |   doubleExp(x,i) =
             let
                 val y = doubleExp(x,i-1)
             in
                 y*y
             end;
Note that we could have written the recursive case simply as
     |   doubleExp(x,i) = doubleExp(x,i-1) * doubleExp(x,i-1);
However, it would take considerably more time to compute the function this way, as computations would then be repeated an exponential number of times.

Return to Top

Solutions for Section 3.5

Exercise 3.5.1

     fun cat(nil,M) = M
     |   cat(x::xs,M) = x::cat(xs,M);
That is, we recursively put the tail of L in front of M, and finally put the head of L in front of all, using the cons operator.

Return to Top

Solutions for Section 3.6

Exercise 3.6.4

If the list of roots is empty, the polynomial 1, represented by the list [1.0], is appropriate. If the list is not empty, then let p be the first root. We may recursively construct the polynomial for the rest of the roots and multiply it by x-p, or in list representation [~p,1.0]. The following lines implement this idea.
     fun polyFromRoots(nil) = [1.0]
     |   polyFromRoots(p::ps) =
             pmult([~p,1.0],polyFromRoots(ps));

Return to Top