CPS 352/543 Practice Problems



Outline


Fundamentals

  1. Short-answer questions. Be concise, but complete, in your responses.

  2. What is a programming language?

  3. What is a side effect?

  4. Give a code example of a side effect in C.

  5. List the four major programming paradigms.

  6. List three programming language paradigms other than the object-oriented and scripting paradigms.

  7. Purity in programming paradigms.
    1. For each of the three programming paradigms below, give the concept from the study of computer languages which would make a language pure for that paradigm.
      1. functional:

      2. declarative:

      3. object-oriented:

    2. For each of the three programming paradigms below, give a language which is pure in that paradigm.
      1. functional:

      2. declarative:

      3. object-oriented:

    3. For each of the three programming paradigms below, give a language which is impure in that paradigm, and give a specific feature from the language which makes it impure for that paradigm.
      1. functional:

      2. declarative:

      3. object-oriented:

  8. (true / false) Any function that is without side-effect is referentially transparent.

  9. (true / false) Any function that is referentially transparent is without side-effect.

  10. Are all functions without side-effects referentially transparent?

  11. Are all referentially transparent functions without side-effects?

  12. Find a keyword in C or C++ (only special in certain contexts).

  13. Are objects first-class in C++? Java?


Formal Languages and Grammars

  1. Give a regular expression which matches any phrase of exactly three words separated by white space.

  2. Design an unambiguous BNF grammar capable of generating only strings of matched parentheses (e.g., (), ()(), (()), (()())(), but not )(, )(), (()()).

  3. Alter the grammar (not the language) for arithmetic expressions so that it is unambiguous.

  4. Design an unambiguous grammar for the dangling else problem without changing the language.

  5. Design a context-free grammar which generates sentences with start with a capital letter (as in English).

  6. Express .*hw.* as a context-free grammar.

  7. (true / false) Ambiguity in a grammar posses no problem for language generation.

  8. (true / false) There does not exist an infinite language L, where L is regular.

  9. (true / false) Every regular language can be specified using a context-free grammar.

  10. (true / false) Every context-free language is also a regular language.

  11. (true / false) Every regular language is also a context-free language.

  12. (true / false) Every regular language is a context-sensitive language.

  13. (true / false) A set cannot be specified using a context-free grammar.

  14. (true / false) Scheme is a context-free language.

  15. Fill in the blank on the left with the name of a formal language and the blank on the right with the name of a formal grammar.
    C is a _____________ language implemented with a ______________ grammar.

  16. C++ is an X language, but is implemented with a Y grammar.
    1. X is (circle one) i) context-free ii) context-sensitive

    2. Y is (circle one) i) context-free ii) context-sensitive

  17. We said in the class that, surprisingly enough, the abilities of programmers have had little influence on programming language design and implementation historically despite the fact that programmers are the primary users of programming languages! For instance, the ability to nest comments is quite helpful when a programmer desires to completely comment out a section of code which may already contain a comment. However, the designers of C decided to forbid comments from nesting. That is, comments cannot nest in C. Therefore, the following code is syntactically invalid in C:
    /* f() has an error;
       I'll just comment it out for now.
    
    void f() {
    
       /* an integer x */
       int x;
    
       ...
    }
    
     */
    
    Why did the designers of C decide to forbid comments from nesting in C? Why cannot comments nest in C? Be specific and clear in your answer.

    1. Prove that the following BNF grammar is ambiguous.
      <E> ::= <E> + <E>
      <E> ::= <T>
      <T> ::= <E>
      <T> ::= <T> * <E>
      <T> ::= id
      where <E> and <T> are non-terminals and +, *, and id are terminals.

    2. Modify this grammar so that it is unambiguous, but still generates the exact same language as the unmodified version. Can we design an unambiguous version of the grammar which contains only two non-terminals, like the original grammar, and still generates the same language?

  18. Consider the following BNF grammar:
    S aSbS
    S bSaS
    S ε
    where S is a non-terminal, a, b are terminals, and ε is the empty symbol. Prove that this grammar is ambiguous.

  19. Define a context-free grammar in BNF capable of generating only palindromes of binary numbers. A palindrome is a string which reads the same forward as backward. For example, the strings 0, 1, 00, 11, 101, and 100101001 are palindromes while the strings 10, 01, and 10101010 are not. The empty string is not in this language.

  20. Define an unambiguous context-free grammar in BNF capable of generating only palindromes of binary numbers. A palindrome is a string which reads the same forward as backward. For example, the strings 0, 1, 00, 11, 101, and 100101001 are palindromes while the strings 10, 01, and 10101010 are not. The empty string is not in this language.

  21. Matching syntactic entities (e.g., parentheses, brackets, or braces) is an important aspect of all programming languages. Define an unambiguous context-free grammar in BNF capable of generating only balanced strings of parentheses. For example, the parentheses in the strings ((()())()) and ()() are balanced while those in the strings ((()()), )()(, and ())(( are unbalanced. The empty string is not in the language of balanced parentheses.

  22. Define an unambiguous context-free grammar in BNF capable of generating only binary numbers which contain the same number of 0's as 1's. For instance, the numbers 01, 10, 0110, 1010, 011000100111, 000001111011 are in the language, but the numbers 0, 1, 00, 11, 01100010011, 00000111011, and 1111000 are not. The empty string is not in this language. State whether or not your grammar is ambiguous, and if ambiguous, prove it is ambiguous.

  23. Define a BNF grammar for the language consisting of strings which have n copies of the letter a followed by the same number of copies of the letter b, where n > 0. For example, the strings ab, aaaabbbb, and aaaaaaaabbbbbbbb are in the language, but a, abb, ba, and aaabb are not. Is this a regular or context-free language?


Functional Programming in Scheme

  1. Fill in each blank in the sentence below with the name of a programming language paradigm.
    Scheme is a ______________ language with some ______________ features.
  2. What is an S-expression? Be complete.

  3. Functions are first-class in functional languages. What does this mean?

  4. A fictitious pure functional language Q contains the following production in its grammar to specify the syntax of its if construct. <expression> ::= (if <expression> <expression> <expression>) The semantics of an expression generated using this rule in Q are as follows: if the value of the first expression (on the right hand side) is true, return the value of the second expression (on the right hand side). Otherwise, return the value of the third expression (on the right hand side). In other words, the third expression on the right hand side (the so called `else' part) is mandatory.

  5. Why does language Q not permit the third expression on the right hand side to be optional? In other words, why is the following production rule absent from the grammar of Q: <expression> ::= (if <expression> <expression>) Hint: the answer has nothing to do with an ambiguous grammar or prevention of the dangling else problem.

  6. Two criteria on which to evaluate programming languages are readability and writeability. For instance, the verbosity in COBOL makes it a readable, but not very writable, language. All of the parentheses in LISP, on the other hand, make it a neither readable nor writable. Why did the language designers of LISP decide to include so many parentheses in its syntax? What advantage does such a syntax provide at the expense of compromising readability and writeability?

  7. There is little doubt in the programming languages community that all of the parentheses in LISP syntax make LISP programs less readable and writable. Why does LISP use such a syntax?

  8. Next to each of the following items, list all languages from the set {ML, Smalltalk, C++, Scheme, PROLOG} in which the item is considered first-class.
    1. function:

    2. continuation:

    3. object:

    4. class:

  9. Define a recursive function flip in Scheme, COMMON LISP, ML, or Haskell which accepts a list of atoms of arbitrary length as its only argument and returns that list with adjacent elements flipped. In other words, the function alternates elements of a list (i.e., given a list [a1,a2,a3,a4,a5,a6...,an] as an argument, produce [a2,a1,a4,a3,a6,a5,...]). If n is odd, an remains at the end of the resulting list. You must not use any auxiliary functions. Examples (in Scheme):
    > (flip '())
    ()
    > (flip '(a))
    (a)
    > (flip '(a b))
    (b a)
    > (flip '(a b c d))
    (b a d c)
    > (flip '(a b c d e))
    (b a d c e)
    
    Examples in ML:
    flip ([1,2,3,4]);
    val it = [2,1,4,3] : int list
    flip ([1,2,3,4,5,6]);
    val it = [2,1,4,3,6,5] : int list
    flip ([1,2,3]);
    val it = [2,1,3] : int list
    
  10. What is the type of flip?

  11. Write a recursive function cycle in Scheme, COMMON LISP, ML, or Haskell which, given a list L and an integer i, cycles L i times. You must not use any auxiliary functions. Examples (in ML):
    cycle (1, [1,4,5,2]);
    val it = [4,5,2,1] : int list
    cycle (2, [1,4,5,2]);
    val it = [5,2,1,4] : int list
    cycle (6, [1,4,5,2]);
    val it = [5,2,1,4] : int list
    
  12. Write an recursive function makeset in Scheme, COMMON LISP, ML, or Haskell which takes a list of integers as input and removes the repeating elements (the answer is returned as a list). Thus, makeset ([1,3,4,1,3,9]) returns [1,3,4,9]. The order in which the elements are returned in the answer does not matter, as long as there are no repeats. Use no auxiliary functions, except member.

  13. Define a recursive function square in Scheme, COMMON LISP, ML, or Haskell which accepts only a positive integer n and returns the square of n (i.e., n2). Examples (in Scheme):
    > (square 1)
    1
    > (square 2)
    4
    > (square 3)
    9
    > (square 4)
    16
    
    Examples (in ML):
    square (0);
    val it = 0 : int
    square (1);
    val it = 1 : int
    square (2);
    val it = 4 : int
    square (3);
    val it = 9 : int
    square (10);
    val it = 100 : int
    
    Answers such as, and including, the following are not recursive and are therefore incorrect and earn no credit:
    (define square
       (lambda (n)
          (* n n)))
    
    (define square
       (lambda (n)
          (cond
             ((eqv? 1 n) 1)
             (else (* (* n n) (square 1))))))
    
  14. Write a recursive function sumpair in Scheme, COMMON LISP, ML, or Haskell which accepts only a list of integers as an argument and returns a pair consisting of the sum of the even positions and the sum of the odd positions of the list. You should not use any auxiliary functions. Examples:
    sumpair ([1,2,3,4]);
    val it = (4,6) : int * int
    sumpair ([1,2,3,4,5,6]);
    val it = (9,12) : int * int
    sumpair ([1,2,3]);
    val it = (4,2) : int * int
    
  15. Define postorder traversal in Scheme.

  16. Define member and member* in Scheme.

  17. Develop a linear-time version of reverse (hint: do not use append, use cons only, and follow the 11th Commandment).

  18. Define a recursive Scheme procedure reverse which accepts only a list of atoms lat as an argument and reverses the elements of lat in linear time (i.e., time proportional to the size of lat, O(n), where n is the number of elements in lat). You may use only define, lambda, let, cond, null?, cons, car, and cdr in reverse. You may not use append or letrec in your solution. Examples:
    > (reverse '(1 2 3 4 5))
    (5 4 3 2 1)
    > (reverse '(1))
    (1)
    > (reverse '(2 1))
    (1 2)
    > (reverse '(nite and day))
    (day and night)
    > (reverse '(1 (2 (3)) (4 5)))
    ((4 5) (2 (3)) 1)
    
  19. We know let is syntactic sugar for lambda. Re-write the following let expression as a semantically equivalent lambda expression while maintaining the bindings of a to 1 and b to (+ a 1):
    (let ((a 1))
       (let ((b (+ a 1)))
          (+ a b)))
    
  20. We know let is syntactic sugar for lambda. Re-write the following let expression as a semantically equivalent lambda expression while maintaining the bindings of sum to the recursive function:
    (let ((sum (lambda (s l)
        (cond
           ((null? l) 0)
           (else (+ (car l) (s s (cdr l))))))))
       (sum sum '(1 2 3)))
    
  21. We demonstrated in class that let is not primitive, but rather just syntactic sugar for lambda. Re-write the following let expression as a functionally equivalent lambda expression while maintaining the binding of the name sum to the recursive function. You may not use define to bind sum to the recursive function. Note: Be very careful in specifying your answer. Here, (something perhaps perceived as insignificant as) including an extra (open or close) parenthesis or omitting a necessary (open or close) parenthesis can be the difference between a correct an incorrect response. Because of the nature of this problem, omitting a necessary parenthesis or including an extra one is much more significant than, for example, missing a semicolon at the end of the line of C code.
    (let ((sum (lambda (s l)
        (cond
           ((null? l) 0)
           (else (+ (car l) (s s (cdr l))))))))
       (sum sum '(1 2 3 4 5)))
    
  22. We showed in class how any letrec expression can be mechanically rewritten as a let expression with equivalent semantics. In other words, the letrec construct is superfluous. It turns out that the let construct is also superfluous. Re-write the following Scheme member? function (within a purely functional setting) without the let while maintaining the binding of head to (car lat) and tail to (cdr lat). Only define one function. Furthermore, do not use letrec, set!, or any imperative features, and do not compute any single subexpression more than once.
    (define member?
       (lambda (a lat)
          (let ((head (car lat)) (tail (cdr lat)))
             (cond
                ((null? lat) #f)
                ((eqv? a head) #t)
                (else (member? a tail))))))
    
  23. We demonstrated in class that let, let*, and letrec each are not primitive (i.e., fundamental), but rather just syntactic sugar for lambda.

  24. Below is the grammar for λ-calculus:
    <expression> ::= <identifier>
    <expression> ::= (lambda ( <identifier> ) <expression> )
    <expression> ::= ( <expression> <expression> )
    Note that with λ-calculus it is not possible to generate a function which accepts more than one argument.

  25. Re-write the following Scheme program in λ-calculus.
    ((lambda (a b) (+ a b)) 1 2)
    
  26. Re-write the following Scheme program in λ-calculus.
    (let* ((x 1) (y (+ x 1)))
       ((lambda (a b) (+ a b)) x y))
    
  27. List the two ways boolean false is represented in COMMON LISP?

  28. What are the only two primitive data types in LISP?
    1. atom and list

    2. number and list

    3. symbol and list

  29. Why doesn't ML or Haskell have an if (<condition>) then <expression>; expression?


Binding and scope

  1. (T/F) The meaning of an expression with no free variables is fixed.

  2. There are many times in the study of programming languages. For example, variables are bound to types in C at compile-time (which means that they remains fixed to their type from then for the lifetime of the program). In contrast, variables are bound to values at run-time (which means that a variable's value is not bound until run-time and can change at any time during run-time. In total, there are six classic times in the study of programming languages, of which compile- and run-time are two. Give an alternate time in the study of programming languages, and an example of something in C which is bound at that time. Hint: There are two times after compile-time, but before run-time.

  3. Mention five binding times in the study of programming languages, arrange them in order from earliest to latest, and give a specific example of something bound during each time.

  4. List 2 specific concepts by name which are examples of late-binding (i.e., dynamic binding) in programming languages. Describe what is being bound to what in each example.


Evaluating Scheme code. Recall, Scheme uses static scoping.

  1. Consider the following Scheme code:
    (define i 0)
    
    (define f
       (lambda (lat)
          (cond
             ((null? lat) (cons i '()))
             (else (let ((i (+ i 1)))
                      (let ((i (+ i 1)))
                         (cons i (f (cdr lat)))))))))
    
    What is the result of (f '(a b c)) ?

  2. Consider the following Scheme code:
    (define i 0)
    
    (define g
       (let ((i (+ i 1)))
          (let ((i (+ i 1)))
             (lambda (lat)
                (cond
                   ((null? lat) (cons i '()))
                   (else (cons i (g (cdr lat)))))))))
    
    What is the result of (g '(a b c)) ?

  3. Consider the following skeletal program written in a block-structured programming language:
    program main;
    var x: integer;
    procedure p1;
    var x: real;
    procedure p2; begin
    ...
    end;
    begin
    ...
    end;
    procedure p3; begin
    write(x);
    end;
    begin
    ...
    end.
    1. If this language uses static scoping, what is the type of the variable x printed in procedure p3?

    2. If this language uses dynamic scoping, what is the type of the variable x printed in procedure p3?

    3. (true / false) A language which uses dynamic scoping can also do static type checking.

    4. Can we use static type checking in a dynamically scoped language?

    5. Can we use dynamic type checking in a statically scoped language?

    6. (true / false) A language which does dynamic type checking can also use static scoping.

  4. Consider the following Ada skeletal program:
    procedure Main is
       X : Integer;
       procedure Sub3; -- This is a declaration of Sub3.
                       -- It allows Sub1 to call it
       procedure Sub1 is
          X : Integer;
          procedure Sub2 is
             begin -- of Sub2
             ...
             end; -- of Sub2
          begin -- of Sub1
          ...
          end; -- of Sub1
     
       procedure Sub3 is
          begin -- of Sub3
          ...
          end; -- of Sub3
    
       begin -- of Main
       ...
       end; -- of Main
    
    Assume that the execution of this program is in following order:
    Main call Sub1
    Sub1 call Sub2
    Sub2 call Sub3
    1. Assuming static scoping, which declaration of X is the correct one for a reference to X in the following:
      1. Sub1

      2. Sub2

      3. Sub3

    2. Repeat part (a), but assume dynamic scoping.
      1. Sub1

      2. Sub2

      3. Sub3

  5. Consider the following Scheme program:
    (define g
    (lambda (f)
    (f)))
    
    (define ...
    ...
    (c)))
    
    (define a
    (lambda ()
    (b '(c d e))))
    
    (a)
    
    1. Draw the sequence of procedures on the run-time stack (horizontally, where it grows from left to right) when e is invoked (including e). Clearly label local variables and parameters, where present, in each activation record on the stack.

    2. Assuming dynamic scoping and shallow binding, what value returned by e?

    3. Assuming dynamic scoping and ad-hoc binding, what value returned by e?

    4. Assuming static scoping, what value is returned by e?


Data abstraction

  1. Why is (null? (extend-env .y 14 (empty-env))) bad code? How can we prevent it?

  2. What is the scope of each variable introduced with cases?

  3. Can you always convert a closure representation into a data-structure representation? Can you always convert a data-structure representation into a closure representation?

  4. Clearly explain why the following C++ code does not compile successfully. Explain why the Scheme (define-datatype ...) construct does not suffer from this problem (Java also does not suffer from this problem). Augment the following code (in place) so that it will compile successfully.
    union bintree {
    
       struct {
          int number;
       } leaf;
    
       struct {
          int key;
          union bintree left;
          union bintree right;
       } interior_node;
    } B;
    
  5. Consider the following BNF definition of a list.
    <L> ::= <A>
    <L> ::= <A><L>
    <A> ::= − 231 − 1 | ... | 231 − 1 (i.e., ints)
    <A> ::= any~floating~point~number~of~type float
    <A> ::= a | b | c | ...| z (i.e., chars)

    Define a variant record list in C++ for this list data structure. The data type must be inductive and must completely conform (i.e., naturally reflect) to the grammar above. Do not use more than ten lines of code in your definition of the data type, and do not use a class or any other object-oriented features of C++.

  6. Consider a data type of stacks of values with the following interface:
    • (empty-stack) = s⌉, where (empty-stack? s) = #t
    • (push (pop s) e ) = s
    • (pop (push se )) = s
    • (top (push se )) = e
    • (empty-stack? s⌉) = { #t if s⌉ = (empty-stack), and #f otherwise }
    where v⌉ means `the representation of data v'.

    Implement this interface using a closure representation for the stack.

    Hint: the functions empty-stack and push are the constructors, and the functions pop, top, and empty-stack? are the observers. Therefore, your closure representation of the stack must take only a single atom argument and use it to determine which observation to make. Call this parameter message. The messages can be the atoms 'empty-stack, 'top, or 'pop. If a message other than one of these three is passed to the closure representation of the stack, return the atom 'error. The implementation requires approximately twenty lines of code and therefore must fit neatly below.

  7. Consider the following definition of a data type expression:
    (define-datatype expression expression?
       (literal-expresssion
          (literal_tag number?))
       (variable-expression
          (identifier symbol?))
       (conditional-expression
          (clauses (list-of expression?)))
       (lambda-expression
          (identifiers (list-of symbol?))
          (body expression?))
       (application-expression
          (operator expression?)
          (operands (list-of expression?))))
    
    Define a function unparse-expression that converts an abstract syntax representation of an expression (using the expression data type given above) into a concrete-syntax (i.e., list-and-symbol) representation of it. The function unparse-expression maps an abstract syntax representation of a λ-calculus expression into a concrete-syntax representation (in this case, a list-and-symbol S-expression) of it.

  8. [EOPL] Exercise 2.26 on p. 68.

  9. Implement an abstract-syntax representation of the environment.

    1. Take expressions defined as follows (extend-env syms_n vals_n ... (extend-env syms_1 vals_1 (empty-env))) and define them using EBNF (a concrete syntax) For instance, <env-rep> ::= ??? <env-rep> ::= ???

    2. Take that concrete syntax and represent it as an abstract syntax.

    3. Define the datatype using (define-datatype ...).

    4. Define the environment implementation.


Environment-passing Interpreters (or programming language concepts)

  1. Explain the difference between a compiler and an interpreter.

  2. Consider the following code fragment which is assumed to be in a block-structured language with static scoping like Pascal:
    program quiz;
    
    var i : integer;
        A : array [1..200] of integer;
    
    procedure f(x,y); begin
       i := x+y;
    end;
    
    begin (* of main program *)
       i := 2;
       A[i] := 99;
       f(i, A[i]);
       println(i);
       println(A[i]);
    end.
    
    The output of this program is:
    2
    99
    
  3. Which parameter-passing mechanism will produce this output? Defend your answer.

  4. Give a simple function (and an invocation of it) that produces different results when it uses the pass-by-value-result parameter passing mechanism than when it uses pass-by-reference. Clearly explain how one of the mechanisms produces different results than the other. Do not give anything more elaborate than 5 lines of code.

  5. What is a closure and how is one created at run-time in Scheme?

  6. Use closures to define a simple counter creation mechanism make-counter. For example:
    > (define counter1 (make-counter 1))
    > (define counter2 (make-counter 10))
    > (define counter3 (make-counter 2))
    > (counter1)
    2
    > (counter2)
    11
    > (counter1)
    3
    > (counter1)
    4
    > (counter3)
    3
    > (counter3)
    4
    > (counter2)
    12
    > (counter2)
    13
    > (counter2)
    14
    
    Only define one function.

  7. List three concepts from programming languages aside from scoping, parameter passing, and recursion.

  8. (true / false) In Scheme, local variables have unlimited extent (i.e., unlimited lifetime).

  9. Define a function which demonstrates the difference between call-by-result and call-by-value-result.

  10. What is a thunk?

  11. Call-by-name and call-by-need are two different implementations of lazy evaluation. How do these two implementation differ?

  12. What is the motivation for lazy evaluation?

  13. Consider the following definition of a procedure try in language mystery.
    function try (a, b) =
       if a = 0 return 1
       else return b;
    
    Is it advisable that mystery use normal-order evaluation or applicative-order evaluation? Explain with reasons.

  14. As discussed in class, some feel lazy evaluation encapsulates all other parameter-passing methods. For example, if the actual is a constant expression, lazy evaluation behaves just like call-by-value. If the actual is a scalar variable, lazy evaluation behaves just like call-by-reference.

    1. Which parameter passing mechanism does lazy-evaluation behave like if the actual is an array element?

    2. Which parameter passing mechanism does lazy-evaluation behave like if the actual is an expression with a reference to a variable which is also accessible within the program?


  15. Scheme and COMMON LISP uses applicative-order evaluation for function arguments. Given this fact, is it wise for the if in COMMON LISP to be treated as a function or a special syntactic form (i.e., not a function) and why?

  16. The following is an example of an if in COMMON LISP
    (if (atom 'x) 'yes 'no)

  17. Lazy evaluation The Scheme programming language uses call-by-value. In this problem, you will implement a simple lazy evaluation software package in Scheme. Specifically, implement two procedures, freeze and thaw, the analogs of the Scheme built-ins delay and force, respectively. The procedures freeze and thaw have the following syntax:

    (freeze exp)
    returns: a thunk (or, in other words, a promise)
    (thaw thunk)
    returns: result of thawing thunk

    In this implementation, an expression subject to lazy evaluation is not evaluated until its value is required and once evaluated is never re-evaluated, i.e., call-by-need semantics. Specifically, the first time the thunk returned by delay is thawed, it evaluates exp, remembering the resulting value. Thereafter, each time the thunk is thawed, it returns the remembered value instead of re-evaluating exp. For example:
    > (define thunk (freeze (+ 2 3)))
    > (thaw thunk) ; computes (+ 2 3) for the first time 
    5
    > (thaw thunk) ; does not re-compute (+ 2 3); simply retrieves value 5
    5
    
    You may assume that the expression exp passed to freeze is not evaluated at the time freeze is called (i.e., assume freeze itself does not use applicative-order evaluation).

  18. Use lazy evaluation (delay and force) to implement an iterator object in Scheme. Specifically, let an iterator be either the null list or a pair consisting of an element and a promise which when forced returns an iterator. Give a definition for an uptoby function which returns an iterator, and a for-iter function which accepts as arguments a one-argument function and an iterator. These two procedures allow you to evaluate an expression such as

    Note that unlike the built-in Scheme for-each, for-iter does not require the existence of a list containing the elements over which to iterate; the space required for (for-iter f (uptoby 1 n 1)) is, therefore, (1), rather than O(n).


Types

  1. Java, ML, and Haskell are strongly typed languages. What does this mean?

  2. List the five primitive data types in ML.

  3. How is boolean false represented in ML?

  4. List three specific features of ML or Haskell not found in imperative languages like Pascal, C/C++, or Java.

  5. ML or Haskell uses (circle one): coercion or casting, and why?

  6. List two ways in COMMON LISP and two ways in ML to represent the empty list.

  7. (true / false) ML has the equivalent of the quote (i.e., ') function in Scheme or COMMON LISP.

  8. Give one reason why you might want to curry a function?

  9. (circle all that apply) Lists in (ML, LISP, PROLOG, Smalltalk) must be homogeneous.

  10. (circle all that apply) Lists in (ML, LISP, PROLOG, Smalltalk) can be heterogeneous.


Type systems

  1. Define an ML or Haskell type triplelist as a list of triples, where the first two components of the triple must have the same type and the third component is of some (possibly) different type. For example:
    - val scores = [(10,10,3.2), (20,15,1.1), (5, 1003, 0.74)]:
                   (int, real) triplelist;
    val scores = [(10,10,3.2),(20,15,1.1),(5,1003,0.74)]:
                 (int,real) triplelist
    - val employees = [("lowgator", "linus", 1), ("lowell", "lucy", 2)]:
                      (string, int) triplelist;
    val employees = [("lowgator","linus",1),("lowell","lucy",2)]:
                    (string,int) triplelist
    
  2. Using higher-order functions, define an ML or Haskell function string2int which accepts only a string representation of an integer and returns the corresponding integer. For example:
    - string2int("123");
    val it = 123 : int
    - string2int("321");
    val it = 321 : int
    - string2int("0");
    val it = 0 : int
    
    Main> :type string2int
    string2int :: [Char] -> Int
    Main> string2int "0"
    0
    Main> string2int "123"
    123
    Main> string2int "321"
    321
    Main> string2int "5643452"
    5643452
    
    Hint: only requires 2 lines of code.

  3. You may assume the ML or Haskell ord function which returns the integer representation of its ascii character argument. For example,
    - ord(#"0");
    val it = 48 : int
    - ord(#"8");
    val it = 56 : int
    
    Prelude> :type ord
    ord :: Char -> Int
    Prelude> ord '0'
    48
    Prelude> ord '8'
    56
    
    Hint: the expression ord(c)-ord('0') returns the integer analog of the character c when c is a digit (see above). The solution to this problem only consists of 2 lines of code.

  4. Define a Haskell function permutations which accepts only a list lst as an argument and returns a list of all permutations of lst (i.e., a list of lists). For instance,
    Main> permutations [1,2]
    [[1,2],[2,1]]
    
    Main> permutations [1,2,3]
    [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
    
    Main> permutations [1,2,3,4]
    [[1,2,3,4],[1,2,4,3],[1,3,2,4],[1,3,4,2],[1,4,2,3],[1,4,3,2],[2,1,3,4],[2,1,4,3],[2,3,1,4],[2,3,4,1],[2,4,1,3],[2,4,3,1],[3,1,2,4],[3,1,4,2],[3,2,1,4],[3,2,4,1],[3,4,1,2],[3,4,2,1],[4,1,2,3],[4,1,3,2],[4,2,1,3],[4,2,3,1],[4,3,1,2],[4,3,2,1]]
    
    Main> permutations ["oranges", "and", "tangerines"]
    [["oranges","and","tangerines"],["oranges","tangerines","and"],["and","oranges","tangerines"],["and","tangerines","oranges"],["tangerines","oranges","and"],["tangerines","and","oranges"]]
    
    Hint: only requires 6 lines of code.

  5. What is the motivation for higher-order functions, such as map, foldl, or foldr?

  6. Defining a function mystery with two parameters, in the SML environment, produces the following response:
    val mystery = fn: int list -> int list -> int list
    
    List everything you can determine from the type above about the definition of mystery as well as the ways which it can be invoked.

  7. Consider the following ML function:
    fun f(g, x) = g(g(x));
    
    1. What is the type of function f?

    2. Is function f polymorphic?

    3. Can function f be rewritten as a C++ template function?

  8. What is the motivation for currying?

  9. What is the motivation for partial evaluation?

  10. What is the difference between currying and partial evaluation?

  11. Describe what a partial evaluator would need to do to specialize the C power function below into a function that computes squares?
    int power (int base, int exponent) {
       int product = base;
       for (int i=1; i < exponent; i++)
           product = product*base;
       return product;
    }
    
  12. In C++, why is return type not considered when the compiler tries to resolve the call to an overloaded function?

  13. The following are some type and variable declarations using Pascal-style syntax. type
    range = -5..5;
    table1 = array [range] of char;
    table2 = table1;
    var
    x, y: array [-5..5] of char;
    z: table1;
    w: table2;
    i: range;
    j: -5..5;


  14. State which sets of variables are type equivalent under
    1. structural equivalence:

    2. name equivalence:

    3. declaration equivalence:


Control

  1. What is a continuation?

  2. Describe how the following Scheme expression is interpreted: (((call/cc (lambda (k) k)) (lambda (x) x)) "hello again").

  3. Why is tail recursion important?

  4. Consider the following definition of a factorial function in Scheme.
    (define factorial
       (lambda (n)
          (if (zero? n) 1
             (* n (factorial (- n 1))))))
    
    The control context created upon an invocation of factorial requires an ever-increasing amount of memory.

  5. (true / false) The following Scheme function makes a tail-recursive call.
    (define sum
       (lambda (l)
          (if (null? l) 0
             (+ (car l) (sum (cdr l))))))
    
  6. Define a Scheme procedure factorial using continuation-passing style which accepts only a non-negative integer n and returns the factorial of n (i.e., n!).
    1. Re-write factorial (in any language of your choice) so that the control context created upon its invocation is constant. factorial must be written so that it is recursive.

    2. Which language did you use to define factorial?

    3. What is the name attributed to the form in which the new version of factorial is written?

  7. Define a recursive function member in Scheme which accepts only an atom a and a list of atoms lat and returns true if a is a member of lat and false otherwise. Your definition of member must use call/cc to avoid returning back through all the recursive calls whether the item is or is not found in the list.

  8. Define a Scheme procedure gcd* using continuation-passing style. The function accepts only a list of positive integers and returns their greatest common divisor. If a 1 is found anywhere in the list return immediately without performing any additional computations. You may assume the existence of a built-in procedure gcd which returns the greatest common divisor of its only two non-negative integer arguments.

  9. Define a Scheme function product which takes a variable number of arguments and returns the product of them. Define product using continuation-passing style such that no multiplications are performed if any of the list elements are zero. Your definition must not use call/cc.

  10. Define a Scheme procedure factorial using continuation-passing style. The function accepts a non-negative integer n and returns n!.

  11. Does the following definition of product perform any unnecessary multiplications? Explain why or why not with reasons.
    (define product
      (lambda (lon)
        (call/cc
         (lambda (break)
            (cond
               ((null? lon) 1)
               ((zero? (car lon)) (break 0))
               (else (* (car lon) (product (cdr lon)))))))))
    
  12. Does the following definition of product perform any unnecessary multiplications? Explain why or why not with reasons.
    (define product
      (lambda (l)
        (letrec ((P (lambda (lon break)
            (cond
               ((null? lon) (break 1))
               ((zero? (car lon)) (break 0))
               (else (P (cdr lon)
                        (lambda (rtnval) (break (* (car lon) rtnval)))))))))
       (P l (lambda (x) x)))))
    
  13. Use call/cc to write a program which print the integers from 0 to 9 in a loop. Do not use recursion and do not use a list.

  14. Using continuations define a Scheme procedure while-loop which only accepts two S-expressions representing Scheme code as arguments, where the first is a loop condition and the second is a loop body. For example, the following call to while-loop prints the numbers 0 through 9 one per line:
    (define i 0)
    (define loop-body
       `(begin
          (write i)
          (newline)
          (set! i (+ i 1))))
    
    (while-loop '(< i 10) loop-body)
    
  15. Use continuation-passing style to define a while loop in Scheme without recursion:
    (define while-loop
    (lambda (condition body)...
    ...< i 10)
    '(begin
    (write i)
    (newline)
    (set! i (+ i 1))))
    
    Define a Scheme function while-loop which takes a condition and a body (which are both Scheme expressions) and implements a while loop. Define while-loop using continuation-passing style. Your definition must not use recursion and must not use call/cc.

  16. The following are two threads which cooperate to print I love Lucy.
    (define thread1
      (lambda ()
          (display "I ")
          (pause)
          (display "Lucy.")))
    
                   
    (define thread2
      (lambda ()
          (display "love ")
          (pause)
          (newline)))
    
    The first thread prints I and Lucy. and the second thread prints love and a newline. The activities of these threads are coordinated, i.e., synchronized, by the use of the function pause, such that the interleaving of their output operations writes an intelligible sentence to standard output, in this case, I love Lucy. Use continuations to provide definitions for pause and resume, without using recursion, so that the following main program prints I love Lucy.
    (define readyq (cons thread1 (cons thread2 '())))
    (resume)
    


Logic Programming

  1. PROLOG is a declarative programming language. What does this mean?

  2. Give an example of a declarative programming language other than PROLOG.

  3. Consider the following logical description for the Euclidean algorithm to compute the greatest common divisor of two positive integers u and v: The gcd of u and 0 is u.
    The gcd of u and v, if v is not 0, is the same as the gcd of v and the remainder of dividing v into u. Develop a PROLOG predicate gcd(U, V, W) that succeeds if W is the gcd of U and V, and fails otherwise.

  4. Define a PROLOG predicate member which accepts only two arguments and succeeds if its first argument is a member of its second list argument and fails otherwise. You must define the predicate member as a single rule with only one atomic append proposition in the antecedent. Provide the definition of append. member must only return true once even if the element occurs more than once in the list.

  5. Assume that you are given the definition of the append(X, Y, Z) PROLOG predicate which is true when Z is the result of appending list Y to list X. Use only this predicate to define the nextto(X, Y, L) predicate, which is true when elements X and Y appear directly next to each other in list L. For instance, nextto(1, 2, [3,1,2]) succeeds, but nextto(1, 2, [1,3,2]) fails.

  6. Define a PROLOG predicate sort which accepts two arguments and sorts its first integer list argument and returns the result in its second integer list argument. For example,
    ?- sort([1],S).
    
    S = [1] 
    
    Yes
    ?- sort([1,2],S).
    
    S = [1, 2] 
    
    Yes
    ?- sort([5,4,3,2,1],S).
    
    S = [1, 2, 3, 4, 5] 
    
    Yes
    
    The PROLOG less than predicate is <. For example,
    ?- 3 < 4.
    
    Yes
    ?- 4 < 3.
    
    No
    ?-
    
  7. Develop a PROLOG predicate sum(N, S) where N is a non-negative integer and S is x=0Nx. For example, the goal sum(4, Y). binds Y to 10, the goal sum(4, 8). fails, and the goal sum(0, 0). succeeds.

  8. Consider the following two PROLOG segments:
    /* segment 1 */
    d(X) :- a(X), !, b(X).
    d(X) :- c(X).
    
    /* segment 2 */
    d(X) :- a(X), b(X).
    d(X) :- not(a(X)), c(X).
    
    Assume that the definitions of a, b, and c are the same for both segments. Answer true or false for the following two statements, with justification:
    1. All solutions for d (i.e., values for X) in segment 1 are also solutions for d in segment 2.

    2. All solutions for d in segment 2 are also solutions for d in segment 1.

  9. Recall the following version of the member/2 PROLOG predicate presented in class.
    member(X, [X|_]).
    member(X, [_|Z]) :- member(X, Z).
    
    member(X, L) returns true if the element represented by X is a member of list L and fails otherwise.
    1. Give the response PROLOG produces for the goal member(X, [barb, tom, kit]).

    2. Give the response PROLOG produces for the goal
      not(not(member(X, [barb, tom, kit]))).

    3. Develop a predicate notamember(X, L) which returns true if X is not a member of list L and fails otherwise. Do not use the member/2 predicate in your definition of notamember/2.

    4. Write a PROLOG predicate makeset(L1, L2) that takes list L1 of integers and removes the repeating elements. The result is returned in list L2. So, this predicate has the same functionality as that required for the ML makeset function above. The order in which the elements are returned in the answer does not matter, as long as there are no repeats. Your predicate should not produce duplicate results. Use no auxiliary predicates, except member/2.

  10. Consider the following PROLOG database:
    parent(linus, lucy).
    parent(linus, lori).
    sibling(X, Y) :- parent(M, X), parent(M, Y).
    
    PROLOG will respond to the goal
    sibling(X, Y).
    
    with
    X = lucy
    Y = lucy
    
    Thus, PROLOG thinks lucy is a sibling of herself. Modify the sibling rule so that PROLOG does not produce pairs of siblings with the same elements.

  11. The following table models a nand gate.

    p q p NAND q
    0 0 1
    1 0 1
    0 1 1
    1 1 0

    Develop a nand/3 predicate in PROLOG.

  12. Consider the following PROLOG predicate:
    A :- B, C, D.
    
    where B, C, and D can represent any subgoals. PROLOG will try to satisfy subgoals B, C, and D, in that order. However, might PROLOG satisfy subgoal C before it satisfies subgoal B?

  13. Consider the following PROLOG goal and its result:
    ?- X=0, not(X=1).
    X = 0.
    
    Explain why the result of the following PROLOG goal does not bind X to 1.
    ?- not(X=0), X=1.
    No.
    
  14. (true / false) Variables are bound in PROLOG through side-effect.

  15. (true / false) The order in which the facts and rules appear in a PROLOG program is significant.

  16. (true / false) The order in which the subgoals appear in a particular PROLOG predicate is insignificant (i.e., P :- A, B.P :- B, A., where P is a predicate and A and B are subgoals).

  17. Does PROLOG use short-circuit evaluation?

  18. Provide a PROLOG goal (and the response PROLOG provides to it) to illustrate your answer. Note: The result of the following goal does not prove or disprove the use of short-circuit evaluation in PROLOG.
    ?- 3 \= 4, 3 = 3.
    No.
    
  19. Define a PROLOG predicate reverse(L, R) that succeeds when the list R represents the list L with its elements reversed, and fails otherwise. Your predicate should not produce duplicate results. Use no auxiliary predicates, except append/3.

  20. Explain why the not/1 PROLOG predicate is not a true logical NOT operator. Provide an example in your explanation.

  21. Using only !, fail, and =, define the \= predicate in PROLOG.

  22. Using only !, fail, and ==, define the \== predicate in PROLOG.

  23. Determine what each of the back1, back2, and back3 predicates print in the following PROLOG program (courtesy [PPFC]):
    back1 :- example(X),
             d(Y),
             write(X), write(Y), nl,
             fail.
    
    back2 :- example(X), !,
             d(Y),
             write(X), write(Y), nl,
             fail.
    
    back3 :- example(X),
             e(Y),
             write(X), write(Y), nl,
             fail.
    
    example(1).
    example(2).
    
    d(1).
    d(2).
    d(3).
    
    e(1) :- !.
    e(2).
    
    List what each of the following goals will print.
    1. back1.

    2. back2.

    3. back3.


Object-oriented Programming

  1. What does dynamic binding refer to in the context of object-oriented programming (i.e., the what is bound to what)?

  2. Consider a Smalltalk class Animal which has three instances - fox, goat, and chicken. Also assume a subclass of Animal called Mammal. Let us suppose we wish to create an instance of Mammal called human.
    1. What message is required to do this?

    2. Which object must be the recipient of this message?

    3. Present your answer in Smalltalk syntax.

  3. Give the Smalltalk analog of this* in C++.

  4. List 3 defining features of Smalltalk.

  5. (true / false) In Smalltalk, once an identifier (i.e., a name) is bound to an object of a particular class, that identifier can be rebound to any other object of that same class or a super class, but no other class.

  6. Explain how type checking works in Smalltalk. In other words, how and when is the name of a message bound to its code-body?

  7. Consider the following Java program:
    class Rectangle {
       void rotate() {
          System....
          ...w();
          y.rotateAndDraw();
          x = y;
          x.rotateAndDraw();
       }
    }
    
    Give the output of this program.

  8. Consider the following Java program:
    class A {
       void p() {
          System.out.println("A.p");
       }
       void q() {
          System.out.println("A.q");
       }
       void f() {
          p();
          q();
       }
    }
    
    class B extends A {
       void p() {
          System.out.println("B.p");
       }
       void q() {
          System.out.println("B.q");
          /* calls the q method in the parent class */
          super.q();
       }
    }
    
    public class Mystery {
       public static void main(String[] args) {
          A a = new A();
          a.f();
          a = new B();
          a.f();
       }
    }
    
    Recall that Java uses dynamic binding (i.e., it dynamically binds methods to messages). Give the output of this program.

Return Home