CSC447

Concepts of Programming Languages

Scope

Instructor: James Riely

Scope

  • Scope of an identifier
    • region of text in which it may be used

Common Scope Rules

  • z is in scope after its declaration until end of if

void f (int x) {
  int y = x + 1;
  if (x > y) {
    int z = y + 1;
    printf ("z = %d\n", z);
  }
}
          

Terminology: Occurrences

  • Occurrences of identifiers classified as one of
    • free occurrence has no matching binding
      
      y = 5*x;   // Free occurrences of x and y
                        
    • binding occurrence declares the identifier
      
      int y;    // binding occurrence of y
                        
    • bound occurrence follows matching declaration
      
      int y;    // Binding occurrence of y
      int x;    // Binding occurrence of x
      
      x=6;      // Bound occurrence of x
      y = 5*x;  // Bound occurrences of x and y
                        

Terminology: Occurrences

  • Complete programs usually have no free occurrences of identifiers
  • How do IDEs treat free occurrences?

Not Just Variables

  • Applies to identifiers for
    • normal variables
    • function parameters
    • function type parameters
    • function/method names
    • class names
    • and more

Forward Declarations

  • C, C++ require forward declarations
  • Most other modern languages do not

char f (int x);

char g (int x) {
  return f (x) + f (x);
}

char f (int x) {
  if (x > 0) { 
    return g (x >> 8);
  } else {
    return 1;
  }
}
          

Shadowing - Java


public class C {
  static void f () {
    int x = 1;
    {
      int y = x + 1;
      {
        int x = y + 1;
        System.out.println ("x = " + x);
      }
    }
  }
}
          

$ javac C.java 
C.java:7: error: variable x is already defined in method f()
        int x = y + 1;
            ^
1 error
          

Shadowing - Java

  • Fields have different treatment

public class C {
  static int x = 1;
  static void f () {
    int y = x + 1;
    {
      int x = y + 1;
      System.out.println ("x = " + x);
    }
  }
  public static void main (String[] args) {
    f ();
  }
}
          

$ javac C.java 
$ java C
x = 3
          

Shadowing C

  • C is less strict than Java (on shadowing)

int main () {
  int x = 1;
  {
    int y = x + 1;
    {
      int x = y + 1;
      printf ("x = %d\n", x);
    }
  }
}
          

$ gcc -o scope scope.c 
$ ./scope
x = 3
          

Shadowing Scala

  • Scala is less strict than Java (on shadowing)
  • Indentation is significant here

object C:
  def f () = 
    var x = 1; 
      var y = x + 1; 
        var x = y + 1; 
        println ("x = " + x) 
  def main (args:Array[String]) =  
    f () 
          
 
$ scalac C.scala  
$ scala C 
x = 3 
          

Shadowing Scala

  • Shadowing occurs in the REPL naturally
 
scala> val x = 1 
x: Int = 1 

scala> def f (a:Int) = x + a 
f: (a: Int)Int 

scala> f (10) 
res0: Int = 11 

scala> val x = 2 
x: Int = 2 

scala> x 
res1: Int = 2 

scala> f (10) 
res2: Int = 11 
          

Shadowing Scala

  • REPL behavior corresponds to
 
{ 
  val x = 1; 
  def f (a:Int) = x + a 
  f (10) 
  { 
    val x = 2;  
    x 
    f (10) 
    ... 
  } 
} 
          

C Initialization

  • Which x in the initializer, x + 1

int main (void) {
  int x = 10;
  {
    int x = x + 1;
    printf ("x = %08x\n", x);
  }
  return 0;
}
          

$ gcc -o scope scope.c

$ gcc -Wall -o scope scope.c
scope.c: In function ‘main’:
scope.c:5:7: warning: unused variable ‘x’ [-Wunused-variable]
scope.c:7:9: warning: ‘x’ is used uninitialized in this function [-Wuninitialized]

$ ./scope 
x = 00000001
          

Java Initialization

  • Java requires that all variables be initialized before use.

class C {    
    public static void main (String[] args) {
        int x = 1 + x;
        System.out.printf ("x = %08x\n", x);
    }    
}

x.java:3: error: variable x might not have been initialized
        int x = 1 + x;
                    ^
1 error

Scala Initialization

  • Scala fields are set to 0 before the initialization code is run
  • Recursion is allowed when initializing fields

scala> val x:Int = 1 + x 
x: Int = 1

scala> :javap -p -c -filter -
Compiled from "<console>"
public class  {
private final int x;
public int x();
  0: aload_0
  1: getfield      #22                 // Field x:I
  4: ireturn

public ();
  ...
  10: invokevirtual #28                 // Method x:()I
  13: iconst_1
  14: iadd
  15: putfield      #22                 // Field x:I
  ...
}

Local variables versus fields

  • Recursion disallowed for local variables
  • vals declared in the REPL are fields

val x = "dog"
def f() = { val x:Int = x + 1; x }
          

|def f() = { val x:Int = x + 1; x }
|                        ^
|               x is a forward reference extending over the definition of x
	  

object o { val x:Int = x + 1 }
o.x
          

// defined object o
val res3: Int = 1
	  

Recursive values in scala

  • Recursive values can be useful

val f : Int=>Int = (x) => if x<=0 then 1 else x * f(x-1)
f(5)
          

val f: Int => Int = Lambda/0x000000c801541bf0@731e0bff
val res0: Int = 120
	  

Scala Initialization

  • Try with a list
  • Null pointer exception happening on print, since null != Nil

scala> val xs:List[Int] = 1 :: xs
java.lang.NullPointerException
  ... 28 elided

Scala Initialization

  • Try to code these as our own objects

scala> case class S(head:Int, tail:S)
defined class S

scala> val ss:S = S(1, ss)
ss: S = S(1,null)

          
  • Need to delay evaluation of tail

scala> case class T(head:Int, tail:()=>T)
defined class T

scala> val ts:T = T(1, ()=>ts)
ts: T = T(1,$$Lambda$1324/2038353966@4d500865)

scala> ts.tail().tail().head
res14: Int = 1
            

Scala LazyList

  • LazyList do this for us!
  • #:: does not immediately evaluate its arguments

scala> val ones:LazyList[Int] = 1 #:: ones
val ones: LazyList[Int] = LazyList(<not computed>)

scala> ones.take (5)
res0: LazyList[Int] = LazyList(<not computed>)

scala> ones.take (5).toList
res1: List[Int] = List(1, 1, 1, 1, 1)

Scala LazyLists

  • Lazy evaluation of list elements

scala> def f (x:Int) : LazyList[Int] = { println (s"f(${x})"); x } #:: f(x+1)
f: (x: Int)LazyList[Int]

scala> val xs:LazyList[Int] = f(10)
xs: LazyList[Int] = <not computed>

scala> xs.take(4).toList
f(10)
f(11)
f(12)
f(13)
res12: List[Int] = List(10, 11, 12, 13)

scala> xs.take(4).toList
res13: List[Int] = List(10, 11, 12, 13)

scala> xs.take(6).toList
f(14)
f(15)
res14: List[Int] = List(10, 11, 12, 13, 14, 15)
          

Recursive values in scala

  • Recursive values can be useful

import scala.math.BigInt
val fibs: LazyList[BigInt] =
  BigInt(0) #:: BigInt(1) #::
    fibs
      .zip(fibs.tail)
      .map { (a,b) =>
        println(s"Adding ${a} and ${b}")
        a + b
      }
println(fibs.take(5).toList)
println(fibs.take(8).toList)
          

Adding 0 and 1
Adding 1 and 1
Adding 1 and 2
List(0, 1, 1, 2, 3)
Adding 2 and 3
Adding 3 and 5
Adding 5 and 8
List(0, 1, 1, 2, 3, 5, 8, 13)
val fibs: LazyList[BigInt] = LazyList(0, 1, 1, 2, 3, 5, 8, 13, <not computed>)
	  

Recursive values in scala

  • Recursive values can be useful

import scala.math.BigInt
val fibs: LazyList[BigInt] =
  BigInt(0) #:: BigInt(1) #:: (
    for
      (a,b) <- fibs.zip(fibs.tail)
    yield
      println(s"Adding ${a} and ${b}")
      a + b
  )
println(fibs.take(5).toList)
println(fibs.take(8).toList)
          

Adding 0 and 1
Adding 1 and 1
Adding 1 and 2
List(0, 1, 1, 2, 3)
Adding 2 and 3
Adding 3 and 5
Adding 5 and 8
List(0, 1, 1, 2, 3, 5, 8, 13)
val fibs: LazyList[BigInt] = LazyList(0, 1, 1, 2, 3, 5, 8, 13, <not computed>)