The meaning of "static" vs. "dynamic" in programming languages

With reference to programming languages, something is static if it happens before the code is executed, and dynamic if it happens during execution. In C/C++, what is meant by "before the code is executed" is clear: it means during compile time or link time (when the different compiled bits are "linked" together into an executable). [Note: there are dynamically linked libraries, where linking happens at run time, but that is not something we've delved into!] In interpreted language, the distinction is a little less clear: something is static if it happens when the interpreter reads and parses the code, but before it starts executing the code. What makes it a bit confusing is that the reading&parsing can be intermingled with the code execution.
Important! You must look over these examples carefully and make sure you understand them. They illustrate the difference between *static* and *dynamic*. Here that is the context of errors, but the point is to see that difference between phenomena occurring as the program is executed (dynamic) vs. occurring before execution (static).

Below are examples of the same static error in C/C++, Python and Java. We are missing an "=" when res is supposed to be assigned a value. If the program starts executing we should see the "in modadd!" message get printed before the error. We don't, which is how we know this is a static error.

Static errors: these happen before the code is executed (note: the "in modadd!" message is not printed)
$ g++ ex0.cpp
ex0.cpp: In function ‘int modadd(int, int, int)’:
ex0.cpp:6:11: error: expected initializer before ‘x’
    6 |   int res x + y % n;
      |           ^
ex0.cpp:7:10: error: ‘res’ was not declared in this scope
    7 |   return res;
      |          ^~~
$ ./a.out
bash: ./a.out: No such file or directory
$ python3 ex0.py
  File "~/ex0.py", line 3
    res x + y % n
        ^
SyntaxError: invalid syntax
$ javac Ex0.java 
Ex0.java:4: error: ';' expected
    int res x + y % n;
           ^
Ex0.java:4: error: not a statement
    int res x + y % n;
              ^
2 errors
$ java Ex0
Error: Could not find or load main class Ex0
Caused by: java.lang.ClassNotFoundException: Ex0

Below are examples of the same dynamic (runtime) error in C/C++, Python and Java. We are dividing by zero with "% n", since parameter n is zero. If the program starts executing we should see the "in modadd!" message get printed before the error. We do see it, which is how we know this is a dynamic error.

Dynamic/Runtime errors: these happen during code execution (note: the "in modadd!" message *is* printed)
$ g++ ex1.cpp
$ ./a.out 
in modadd!
Floating point exception
$ python3 ex1.py 
in modadd!
Traceback (most recent call last):
  File "~/ex1.py", line 6, in 
    k = modadd(4,5,0)
  File "~/ex1.py", line 3, in modadd
    res = x + y % n
ZeroDivisionError: integer division or modulo by zero
$ java Ex1
in modadd!
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Ex1.modadd(Ex1.java:4)
	at Ex1.main(Ex1.java:8)

ACTIVITY


Dynamic scope vs. static (aka lexical) scope

Variables are bindings of names to values. When we say in C int x = 42;, we are binding the name x to the value 42 (as an int). Of course, over the whole program there may be many different bindings of the name x to different values. The scope rules for a language determine which binding is in effect when a particular name is used.

TODO: Below are equivalent Python, C++ and Java programs. They all have the same error, namely that there is no binding for a variable named "y" in scope when we try to use "y" in the function foo. Test, by compiling and/or running the code, possibly adding print statements if need be, whether this is a static or dynamic error in each of these languages. How do you know?

Important! Do your testing first, then read the following!

C/C++ and Java are statically scoped languages (aka lexically scoped). The rule in those languages is to look at compile time for the binding that is in use based on the program text at and around the spot where the variable is referenced. So if, at compile time, no binding for the variable exists, it is an error!

Python has local variables, like we are used to, which are statically scoped, and global variables which are dynamically scoped. Local variables, by the way, means variables that are bound to values within a function or class definition. You don't explicitly declare variables in python like you do in C/C++ or Java, of course. Instead, when a variable is assigned a value within a function or class, a binding (local to that function or class) is created. This means, by the way, that if you want some global variable z to be modified in a function you have a conundrum: writing z = 42 creates a new local variable, so the global variable z is unchanged. To get around this you add the declaration global z to the function before your first use of the name "z", which indicates that you want to use the global binding for z rather than create a new one. At any rate, here are the two big takeaways:

  1. If you accidentally use a variable name in a function (without it appearing first on the left-hand side of an assignment), the interpreter chugs along, perfectly happy, just assuming that, by the time you *execute* the function, there will be a global binding for that name. Note: this goes for *functions* as well as variables. For example:
    def foo(x):	    
        return x + y
    ... is perfectly OK. It is only not OK if you try to call foo without first defining a global binding for y.
    Warning! This means that typos in variable names typically don't get caught until you start testing ... always, of course, assuming you do actually test.
  2. If you want to assign a value to a global variable within a function, you need to make a "global" declaration. For example:
    def bar(x):	    
        global count
        count += 1
        return x*x

Dynamic Typing

This is the main event! C/C++ and Java are all *statically typed* languages. This means that the types of variables and expressions are known before the code is executed, which for these languages means types are known at compile time. Python is *dynamically typed*. This means that types of variables and expressions are not known until the code is executing. The interpreter doesn't know what the type of an expression is until it has actually done the evaluation. If you think about it, this is inevitable just from the fact that, for example, lists are heterogeneous. Consider:
for i in range(0,len(L)):
    what is the type of L[i]????
Since L can contain anything, you cannot possibly know the type of L[i] until you evaluate it and see.

Important! The main issue with *types* in a language is to figure out:

  1. whether a given operation / function call is legal at a particular point in the execution of the program and,
  2. if the operation / function call is indeed legal, which actual operation or function will be executed.
Our goal is to see whether these decisions are static or dynamic in Python, C/C++ and Java.

ACTIVITY
  1. Create the following three source code files on your machine:

    Each of these has a call of function foo that is illegal due to type errors. Determine/demonstrate by compiling and/or running these programs in which language this is a *static* error and in which language it is a *dynamic* error.

    For the language(s) in which this is a static error, explain *why* the error is detected prior to executing the code. For the language(s) in which this is a dynamic error, explain *why* the error is not detected until we are in the middle of executing the code.

  2. In each of the three ex3 source code files, change x * y to x + y, then try your three tests all over again. For one of them there is now no error ... can you explain that change?
  3. Run the version of ex3.py with the x + y, as python3 -i ex3.py and try the following calls:
    	    foo(34,22)
    	    foo(34,True)
    	    foo("abc","xy")
    	    foo("abc",3)
    	    foo([3,4],[5,6])
    	    foo({3,4},{5,6})
    	  
    Important! Can you explain what's happening here? What determines which ones are errors and which are not?

Duck typing

The moral of the previous section is that Python is a dynamically typed language: it is not determined whether an operation in your program is legal until the interpreter actually tries it as the program is executed. When an operation or function call is legal, which particular operation or function call you actually get is also determined as the program is executing.

TODO: Create a file with the following two functions, which both do the same thing: they print out an input list of strings, one per line.

Try running both versions on the following inputs:

printOnLinesVx(["red","fish","blue","fish"])
printOnLinesVx(["red",42,"blue",(4,5)])
printOnLinesVx({"red","fish","blue","fish"})
printOnLinesVx({"foo":"red","bar":"fish","rat":"blue","dog":"fish"})
printOnLinesVx("absolutely")
printOnLinesVx(42)
	
Important Questions!
1. Which version is more "general", i.e. can be used on more inputs?
2. What properties does the input parameter L need in order for version 1 to execute without a type error?
3. What properties does the input parameter L need in order for version 2 to execute without a type error?
4. Is there any instance in which there is no type error, but the code doesn't produce the output you think it should?

Discussion: What you should have seen is that you might write a function with a list in mind but, in fact, what matters is what operations/functions/methods you relied on. Anything else that has those same operations/functions/methods defined for it can be passed into the function and it will still work ... at least in the sense of not having type errors. The Python community has a cute name for this behavior: duck typing. This comes from the saying "if it swims like a duck and quacks like a duck then it is a duck". So if you wrote your function with a list in mind and used len(·) and subscripting with [ ]s, you might say "if it works with len(·) like a list and is subscriptable with [ ]s like a list then it is a list." Thus, as far as your function is concerned, a string is a list! Or a duck ... I'm getting confused.

Be a good python programmer and make the most generally useful function you can!

Consider the function doit(L) defined below:

TODO: The doit() function was designed for lists of strings, and it does what it's supposed to do when that's what it is given. Rewrite doit() to work more generally, so that all of the following calls work properly:

>>> doit(["foo","bar","rat","dog"])
foo
 bar
  rat
   dog
>>> doit("NAVY")
N
 A
  V
   Y
>>> doit(["NAVY","ARMY",42,(3,4)])
NAVY
 ARMY
  42
   (3, 4)
>>> doit({"foo","bar","rat","dog"})
bar
 dog
  foo
   rat
>>> doit(range(1,5))
1
 2
  3
   4
Important! Can you explain how it works on the last one!?!?!?


Christopher W Brown