Homework #4 Solution Sketches 1) FreePascal as most implementations of Pascal does not check the discriminant of a variant record when a field is accessed. Consider the following codes: program simpletest; type myvarrec = record case flag: boolean of false: (x: integer); true: (y: real); end; var x: myvarrec; begin x.flag := true; x.x := 4; end. When this variant record contains an integer, we can assign flag the value false and likewise for real and true. Most implementations of Pascal, including FreePascal, do not however detect an error when a programmer attempts to reference a field which does not exisit in the current definition (as running the code above illustrates). 2) a) As said in class, and as many textbooks mention, C uses structural type equivalence for everything except structs and unions (where it uses name equivalence). Therefore, it would seem that arrays are structurally equivalent in C. However, since an array name in C is simply a pointer to the first element, the language internally uses only pointer rules and comparisons when dealing with arrays. A pointer to an integer array of size 10 can be assigned to one of size 100. See, e.g., main() { typedef int * A; typedef int * B; A a = malloc(sizeof(int)*100); B b = malloc(sizeof(int)*10); b = a; } This really works because the arrays are structurally equivalent, in so much as their pointers are equivalent. So, this is one possible answer, which is also the answer to part (b). On the other hand, if you literally think about equivalence in terms of expressions involving only array variables, we can try something like the following: main() { typedef int A [10]; typedef int B [10]; A a; B b; a = b; } This doesn't work, showing that A and B are not structurally equivalent. main() { typedef int A [10]; A a1, a2; a1 = a2; } This doesn't work, showing that A and B are not name equivalent. Let's try: main() { typedef int A [10]; typedef A B; typedef int C[10]; A a; B b; C c; b = a; } This doesn't work either, leading us to conclude that none of the known forms of type equivalence apply here. b) see above c) structs in C use name and declaration equivalence. main() { typedef struct intandfloat { int x; float y; } IntAndFloat; typedef struct floatandint { int x; float y; } FloatAndInt; IntAndFloat A; FloatAndInt B; A = B; } This doesn't work, showing that structs A and B are not structurally equivalent. main() { typedef struct intandfloat { int x; float y; } IntAndFloat; IntAndFloat A, B; A = B; } This works, showing that structs A and B are name equivalent. main() { typedef struct intandfloat { int x; float y; } IntAndFloat; typedef IntAndFloat FloatAndInt; IntAndFloat A; FloatAndInt B; A = B; } This work as well, showing that structs A and B also are declaration equivalent. d) unions in C use name and declaration equivalence. main() { typedef union intorfloat { int x; float y; } IntOrFloat; typedef union floatorint { int x; float y; } FloatOrInt; IntOrFloat A; FloatOrInt B; A = B; } This doesn't work, showing that unions A and B are not structurally equivalent. main() { typedef union intorfloat { int x; float y; } IntOrFloat; IntOrFloat A, B; A = B; } This works, showing that unions A and B are name equivalent. main() { typedef union intorfloat { int x; float y; } IntOrFloat; typedef IntOrFloat FloatOrInt; IntOrFloat A; FloatOrInt B; A = B; } This works as well, showing that unions A and B also are declaration equivalent. 3) The are many possible answers. First you must figure out (i) what evaluation order your interpreter is using, (ii) find two languages which each use a different evaluation strategy, and (iii) find an expression which results in a different value in each. (i) Consider the following Scheme codes (now that you know ML, you should be able to easily figure out the following code): (define (a) (a)) (define (test x y) (if (= x 0) 0 y)) Invoking a function such as test as follows will help you determine if your interpreter is using normal- or applicative-order evaluation. (test 0 (p)) (ii) and (iii) The code above helps us determine that Scheme uses applicative-order evaluation and Haskell uses normal-order. Consider the following Scheme code. (define (try a b) (if (= a 0.0) 1.0 b) Evaluating (try 0.0 (/ 1.0 0.0)) generates an error in Scheme. With lazy evaluation there would be no error. Evaluating the expression would return 1.0, because the argument (/ 1.0 0.0) would never be evaluated since it is never needed. Haskell is a popular functional language using lazy evaluation. The following Haskell program and function invocation does not generate a divide by 0 error. module Try where try :: Float -> Float -> Float try a b = if a == 0.0 then 1.0 else b try 0.0 (1.0 / 0.0) Thus, we have given an expression which yields different results between evaluation in applicative-order and evaluation in normal-order. 4) left -> right right -> left sum1 = 46 sum1 = 46 sum2 = 48 sum2 = 44 5) The following C operators associate right to left (see p. 53 of Kernighan and Ritche's (the designers of C) "The C Programming Language," which is on the bookshelf of every self-respecting computer science student and C programmer. ! ~ ++ -- + - * & (type) sizeof ?: = += -= *= /= %= &= ^= |= <<= >>= (notice that they are listed in order of precedence)