CMSI 386 Final Exam Preparation Fall, 2009 ============================================================================== Students best prepare for a final when they have two things: 1. An outline of the topics that were covered in the course 2. A recent final exam, with answers! I would suggest going over the outline, making sure you understand each topic by going back over the course notes AND the relevant sections in the text. Then, try to take the practice exam in test-like conditions: give yourself a couple hours of quiet time and work on the exam. After you are done you can grade yourself; the answers to the exam are at the end of this page. Your actual final will be very much like the practice final here, so take advantage of this page! OUTLINE ------- Programming Languages as a Discipline Why study Evaluation Criteria Major Types of Programming Languages Machine, Assembly, High-level, System, Scripting, Esoteric Paradigms Imperative, Declarative, Structured, Functional, OO, many more Five Example Languages Ruby Very expressive Interactive shell (irb) Fully object oriented Objects, arrays, hashes Blocks Procs Enumerations How to write your own classes Open classes Inheritance Attributes Modules Metaclasses (Object-specific classes) JavaScript Probably the most popular language in the world Terribly misunderstood Beautiful expressive core language buried in tons of crap Must run in a host environment Primitives vs. objects Properties Six types Fairly weakly typed, lots of coercions Arrays and functions are objects this Constructors and Prototypes - not classes! Inheritance is clunky, you probably don't need it Functional programming is the way to go here ML Research language Has many remarkable features Type inference! (Static typing without the verbosity) Designed for functional programming Imperative features there if needed Pattern matching Interactive shell vs compiler Has alphanumeric and symbolic identifiers Can create new operators Rich type system Type variables Currying Sophisticated module system Java Popular applications language Probably already learned it C Popular System Language Fairly primitive - have to do almost everything yourself Not garbage collected, even! Programmer must distinguish pointer from referent Concepts in Program Languages Syntax Syntax vs semantics How to design a syntax Notations: CFG, BNF, EBNF, syntax diagrams Need for micro and macrosyntax Abstract syntax (important) Naming Why study Binding Lifetime of bindings Static, stack, and heap storage Scope: static and dynamic A-lists and Central reference tables for dynamic scope Masking declarations Subroutines as arguments Deep and shallow binding Dangling references Control Flow Seven types of flow Statements, Expressions, Declarations Operators Precedence Associativity Arity Fixity Evaluation Order Short-circuiting Side effects L-values and R-values Initialization and assignment Sequencing Selection If/elsif/else and switch Iteration Logically controlled loops Enumeration controlled loops Exiting from the middle of a loop Iterator objects Recursion Tail recursion Non-determinacy Types Type systems Equivalence Compatibility Inference Checking Strong vs. Weak Static vs. Dynamic Manifest vs. Implicit Inference Overview of common types Subroutines Basic terminology Signatures Parameter association Named Positional Mixed Overloading Default parameters Variadic Function returns Argument passing Semantic in, out, in out Pragmatic By value By value-result By reference By name Aliasing? Generic subroutines What's the point? Subroutines as values (higher order functions) Closures THE FINAL FROM 2007 ------------------- 1. Give macrosyntax rules for expressions in a language in which * The lowest precedence operators are the binary "unless", "if", "while", and "until" operators, which are non-associative. * The next lowest precedence operators are the logical binary operators, which are left associative amongst themselves, but do not associate with each other, meaning that one while can write "A and B and C", one may not write "A and B or C" * The next lowest precedence operators are the relational operators ("<", "<=", "==", "!=", ">=", and ">"), which are non-associative. * Next in precedence come the left associative shift operators ("<<" and ">>") * Next come the left multiplicative operators ("*", "/", and "%") * Next come the left associative additive operators ("+" and "-") * Next come the right associative exponentiation operator ("**") * Next come non-associative unary operators. The unary operators "-" and "not" are prefix, while "!" (for factorial) is postfix. Because these are non-associative, one cannot write "- - 2" or "-5!" or "3!!!". * The most primitive expressions are identifiers, numeric literals, string literals, ranges (of the form "[e1 .. e2]" for expressions e1 and e2), function calls, and of course, parenthesized expressions. Assume the existence of tokens ID, NUMLIT, STRINGLIT, and CALL. 2. Explain what would need to be done to make deep binding work with dynamic scoping, assuming that A-lists were used to implement the scope rules. (Hint: think of turning the A-lists into "A-trees".) 3. Write a JavaScript function, without using eval, that accepts an array of integers a, and a function f, and returns an array of functions, each of which, when called, invokes f on the corresponding element of a. For example, if your function was called g, then calling g([6,3,1,8,7,9], dog) would return an array of functions p such that, for example, calling p[3]() would invoke dog(8). 4. In the Java programming language, if the class Dog were a subclass of class Animal, then objects of class Dog[] would be compatible with the type Animal[]. Write a fragment of Java code that shows that this means that Java is not completely statically typed. Include in your answer a well-written explanation that shows you truly understand the difference between static and dynamic typing. 5. Show the output of the following code fragment under the following four conditions: (a) pass by value, (b) pass by reference, (c) pass by value-result, and (d) pass-by-name. x = 1; y = [2, 3, 4]; function f(a, b) {b++; a = x + 1;} f(y[x], x); print x, y[0], y[1], y[2]; 6. Show the output of the following, assuming dynamic scope and (a) deep binding, and (b) shallow binding. function g(h) { var x = 2; h() } function main() { var x = 5 function f() { print x + 3 } g(f) } main() 7. Assume we wanted to write a function called If in Java or C or JavaScript, such that the call If(c, e1, e2) would return e1 if c evaluated to true, and e2 if it evaluated to false. Show why, in these languages, such a function is absolutely not the same as the conditional expression c?e1:e2. You can show a code fragment that would return different results based on whether the function were called versus the conditional expression were evaluated. 8. One of the freshman tried to write a Ruby dot-product method. It came out like this: def dot(a, b) a.zip(b).map{|x,y| x*y}.inject{|x,y| x+y} end (a) Her first unit test failed. What was it and why did it fail? (b) She fixed that problem with this def dot(a, b) a.zip(b).inject(0){|x,y| x+y[0]*y[1]} end Eventually she wrote a unit test with the first array longer than the second and got a TypeError. She fixed that by raising an ArgumentError if the arguments to dot had different lengths. Show her fixed up method. (c) Then she got fancy and put her method into the array class class Array def *(a) ..............her code here.............. end end The unit tests worked: [3,4,2] * [1,5,0] == 23 for example. But this fancy "*" definition had a very nasty side effect, which, if she did this in real production code, would have probably broken something big time. What went down with this definition? 9. For each of the following code fragments, explain what happens when evaluating them. Make sure you convey in your explanation a thorough understanding of Ruby objects and metaclasses. (a) dog="sparky"; class <not having ML on the final. So, to show that you understand tail-recursion you'll just have to do it in different languages. And, by the way, you'll probably want to show off the tail recursion in a "helper method" (but you already knew that). 11. What does this script print under (a) static scope rules and (b) dynamic scope rules? var x = 1 function f() {return x;} function g() {var x = 2; return f();} print g() + x ANSWERS TO FINAL FROM 2007 ========================== PROBLEM 1 --------- EXP -> EXP1 (("if" | "while" | "unless" | "until") EXP1)? EXP1 -> EXP2 ("and" EXP2)* | EXP2 ("or" EXP2)* EXP2 -> EXP3 (RELOP EXP3)? EXP3 -> EXP4 (SHIFTOP EXP4)* EXP4 -> EXP5 (ADDOP EXP5)* EXP5 -> EXP6 (MULOP EXP6)* EXP6 -> EXP7 ("**" EXP6)? EXP7 -> ("-" | "not")? EXP8 EXP8 -> EXP9 "!"? EXP9 -> ID | NUMLIT | STRINGLIT | CALL | "(" EXP ")" | "[" EXP ".." EXP "]" PROBLEM 2 --------- When you call the passed function, save the existing pointer to the top of the A-list and replace it with a pointer to just before the point that the function was defined. Then enter the bindings for the new function as a "branch" in the A-list (which is now a tree). When the function finally returns, chop off that branch and restore the pointer. PROBLEM 3 --------- function g(a, f) { var b = []; for (var i = 0; i < a.length; i++) { b[i] = function(i){return function(){f(a[i])};}(i); } return b; } PROBLEM 4 --------- If both Dog and Rat are subclasses of Animal, this code Animal[] pets = new Dog[4]; pets[0] = new Rat(); compiles fine but when executed throws an ArrayStoreException, that's right, a *run-time typecheck error*. This means Java is NOT 100% statically typed because this typecheck occurs at run time. A language can only be called "100% statically typed" if all type conflicts are detected at compile time. PROBLEM 5 --------- a) Under call by value, the arguments do not change; so the script prints "1 2 3 4". b) Under call by reference, the increment of b changes x immediately, so the new value of x, namely 2, is used to update a, which is still y[1], and that becomes 2 + 1 = 3, so it prints "2 2 3 4". c) Under call by value/result, the increment of x does not take place until AFTER the subroutine returns. It prints "2 2 2 4". d) Under call by name, b++ changes x immediately so x becomes 2. Then, since a refers to "y[x]" it's now referring to y[2] which then gets 3. The script prints "2 2 3 3". PROBLEM 6 --------- a) 8 b) 5 PROBLEM 7 --------- In Java, C, and JavaScript, ALL function arguments are evaluated before they are called. The evaluation of (p==null ? null : p.value) is null-safe, whereas the call If(p==null, null, p.value) is not -- it throws a NullPointerException if p is null. A more blatant example can be seen in the difference between (true ? 1 : formatMyHardDrive()) and If(true, 1, formatMyHardDrive()). PROBLEM 8 --------- a) The first unit was assert_equal(dot([], []), 0) and it failed because her method returned nil, not 0. b) def dot(a, b) raise ArgumentError if a.length != b.length a.zip(b).inject(0) {|x,y| x+y[0]*y[1]} end c) There is already a "*" in the array class; any new definition *replaces* the old one. Old code using Array*int and Array*string WILL BREAK, because ints and strings can't be converted to arrays. PROBLEM 9 --------- dog="sparky"; class <