LMU ☀️ CMSI 3802
LANGUAGES AND AUTOMATA II
HOMEWORK #3 PARTIAL ANSWERS
  1. In Java
    1. Not a compile-time error
    2. Not a compile-time error
    3. Static Semantic
    4. Static Semantic
    5. Static Semantic (Java does check this at compile time!)
    6. Not a compile-time error (in general)
    7. Not a compile-time error
    8. Not a compile-time error (Java actually allows this)
    9. Static Semantic (booleans cannot be compared)
    10. Not a compile-time error
    11. Syntax (Opening triple-quote must end a line)
    12. Syntax (switch is a reserved word)
    13. Not a compile-time error (Switch expressions FTW)
    1. Scope of variable is entire function in which it is declared, and set to undefined at the beginning of function. (This is JavaScript var.)
      
      var x = 3;       // outer x scope
      function f() {   // inner x scope, begins with x undefined
        print(x);      // undefined
        var x = x + 2; // x assigned undefined+2 i.e. NaN
        print(x);      // NaN
      }
      f();
      
    2. Scope of variable is entire function in which it is declared, but is inaccessible from the beginning of the function until its declaration. (This is JavaScript let, illustrating the temporal dead zone.)
      
      var x = 3;       // outer x scope
      function f() {   // inner x scope, TDZ begins
        print(x);      // Access in TDZ: Runtime error
        var x = x + 2; // x assigned undefined+2 i.e. NaN
        print(x);      // Execution never gets here
      }
      f();
      
    3. Scope of variable is entire function in which it is declared, and is bound to its memory address at the beginning of the function, so any access before the declaration reads garbage.
      
      var x = 3;       // outer x scope
      function f() {   // inner x scope begins with no init, just garbage
        print(x);      // garbage
        var x = x + 2; // x assigned whatever garbage it started with + 2
        print(x);      // garbage+2
      }
      f();
      
    4. Scope begins at the point of declaration, without waiting for the declaration to be complete, so uses before the declaration refer to the nonlocal entity. But because we can start using the identifier right away, it appears as garbage when used in its own declaration.
      var x = 3;       // outer x scope
      function f() {
        print(x);      // still using outer x, prints 3
        var x = x + 2; // inner x scope
        print(x);      // garbage+2
      }
      f();
      
    5. Scope rules can be either whole function or point of declaration, but the compiler will check for, and disallow, uses of an identifier in its own declaration. This is a compile-time check, so the code never runs.
      var x = 3;
      function f() {
        print(x);
        var x = x + 2; // compiler rejects usage of x here
        print(x);
      }
      f();
      
  2. Yes! Suppose g were a function which modified the global variable a as a side effect. Then the expression f(a,g(b)) could yield different results depending on which order the arguments were evaluated. (I am hoping your answer shows an exact case!)
  3. Carlos does not allow recursive structs because structs are value types and are (at least conceptually) stored directly rather than being allocated on the heap. Therefore a “recursive” struct would have infinite size. So when analyzing a struct type T, the compiler will look for any fields that have type T, and if it finds any, will generate an error. The search itself recurses into any fields of the struct which happen themselves to be structs. The code for this, which you can find on GitHub, is:
    function includesAsField(structType, type) {
      // Whether the struct type has a field of type type, directly or indirectly
      return structType.fields.some(
        field =>
          field.type === type ||
          (field.type?.kind === "StructType" && includesAsField(field.type, type))
      )
    }
    
    function mustNotBeSelfContaining(structType, at) {
      const containsSelf = includesAsField(structType, structType)
      must(!containsSelf, "Struct type must not be self-containing", at)
    }
    
  4. The minimum value of a (non-empty) sequence is: (1) the first element, if the sequence has length one, or (2) the minium of the first element and the minimum value of the rest of the sequence. In pseudocode you might write:
  5. function min_element(a)
        if len(a) == 1 then
            return a[0]
        else
            return min(a[0], min_element(a[1..]))
        end
    end
    

    However, this is not tail recursive, and is not an acceptable solution. To make it tail recursive you need the trick where your recursive function accepts a list and the current minimum so far. Here are some solutions to the tail-recursive part. In most cases these would need to be wrapped in a function which did not take in the “accumulator” min-so-far variable.

    def minimum(a, min_so_far = math.inf):
        if not a:
            return min_so_far
        return minimum(a[1:], min(a[0], min_so_far))
    
    double minimum(double *array, int len, double min_so_far) {
        if (len == 0) {
            return min_so_far;
        }
        double new_min = array[0] < min_so_far ? array[0] : min_so_far;
        return minimum(array+1, len-1, new_min);
    }
    
    function minimum(array, minSoFar = Infinity) {
        if (array.length === 0) return minSoFar
        return minimum(array.slice(1), Math.min(a[0], minSoFar))
    }
    
    // Thanks to ChatGPT for this
    func minimum(array []float64, minSoFar float64) float64 {
        if len(array) == 0 {
            return minSoFar
        } else {
            newMin := minSoFar
            if minSoFar < array[0] {
                newMin = array[0]
            }
            return minimum(array[1:], newMin)
        }
    }
    
    minimum([], Min) -> Min;
    minimum([Head | Tail], Min) -> minimum(Tail, min(Head, Min)).
    
    fn minimum(arr: &[f64], min_so_far: f64) -> f64 {
        match arr {
            [] => min_so_far,
            [head, tail @ ..] =>
                minimum(tail, if *head < min_so_far { *head } else { min_so_far })
        }
    }
    
  6. The second friend’s code is passing undefined to the callback of setTimeout. This is an example where delayed execution is called for, so to fix it, you need to replace update(i) with ()=>update(i). There’s another way, too. You can invoke setTimeout with a third argument: setTimeout(1000, update, i). That’s a bit harder to read, but it does work!
  7. Using SonarLint and PMD I found:
    • The file is not in a named package.
    • Should use a utility class (private constructor) since everything is static.
    • Should use interface Map for the variable declaration, not HashMap.
    • Since HashMap was used, the right hand side should have used a diamond.
    • Empty constructor should be documented.
    • Public constructor should be hidden.
    • Should use a constant instead of a method for zero.

    FindBugs and FindSecBugs might find more!