Question

By definition, dynamic typing and dynamic scoping have different purposes. But some cases make me wonder if they can lead to each other sometimes.

  1. Dynamic typing allows a name i.e. identifier to refer to objects of different types at different points at run time, e.g.

    x = 1
    x = 'abc'
    

    When dynamic typing changes the type of the object that a name refers to, does it lead to change of the scope of the name? In the above example, does the scope of the name x change when reaching the second assignment?

  2. Dynamic scoping allows a name to refer to different objects at different points in run time. When the different objects that a name refers to have different types, does it lead to dynamic typing?

Thanks.

Was it helpful?

Solution

Dynamic scoping and dynamic typing have two things in common:

  1. they have something to do with variables, and
  2. they both contain the word “dynamic”.

What does “dynamic” mean here? Something is dynamic if changes with time.

With dynamic typing, the type of a variable may change with time. That means it is no longer meaningful to talk about the type of a variable. Instead, we should talk about the type of the value referenced by a variable. When we reassign a variable to a different value, we still have the same variable. Because it is the same variable, how the variable is scoped does not change.

With dynamic scoping, the visibility of a variable changes with time, or more precisely, with the control flow. Consider the following two functions:

func f() {
  print(x)
}

func g() {
  var x = 42
  f()
}

With lexical scoping, the variable x in the f() function will never refer to a value. Usually, a compiler would reject this program since x was never declared within a scope of f().

With dynamic scoping, this depends on how f() was invoked. If we invoke f() directly, no x variable was assigned. If we invoke g(), a dynamically scoped x variable is assigned. This variable is then still visible during the execution of f().

All of this is complicated and error prone. In fact, dynamic scoping is very similar to a global variable that's visible everywhere. In early programming languages (e.g. many Lisp dialects), dynamic scoping was more common since it's easier to implement that lexical scoping. However, it is avoided by more recent languages.

The only common programming languages I know which support dynamic scoping are Perl and Bash, both through the local keyword. The local keyword creates a new dynamic variable with the given name, which is destroyed when the lexical scope or the current function is left. Consider this Bash program:

print_x() { echo "$1: $x"; }
assign_x() { x="$1"; }

outer() {
  local x=a
  print_x outer #=> outer: a
  assign_x b
  print_x outer #=> outer: b
  inner
  print_x outer #=> outer: b
}

inner() {
  print_x inner #=> inner: b
  local x=1
  print_x inner #=> inner: 1
  assign_x 2
  print_x inner #=> inner: 2
}

outer

What happens here? The print_x and assign_x function will always access the current x variable. When assign_x assigns to x, it changes the current variable. The outer function creates an x variable, reassigns it through assign_x, then invokes the inner function. Here, we still have access to the current x from the outer function, before we create another x variable. Whatever we do to x now affects this new x, but leaves the old x alone. Only when we leave the inner function is the temporary x deleted, and the previous x becomes visible again.

This is important: there are two distinct x variables in this program. Changing (the type of) on variable does not affect the other.

If the effect of dynamic typing is needed in a language that does not support it, we must pass the data as function arguments instead. For the variable to be reassignable, we must pass it by reference, or as a pointer. However, dynamic scoping allows us to pass data through multiple function calls that do not include this parameter, so this is not a perfect solution.

Dynamic typing and dynamic scoping do not lead to each other. Dynamic scoping is rarely seen, and even less rarely without dynamic typing. However, there are many dynamically typed languages without dynamic scoping (e.g. Python), and even some languages with dynamic scoping but without dynamic typing (e.g. Bash: variables do have types, but it's complicated).

Appendix: Dynamic scoping with static typing

There is a statically typed mainstream language with a feature similar to dynamic scoping: Java with its checked exceptions.

In a statically typed language, a function would have to declare the types of the dynamically scoped variables it uses, as they form a part of its interface. I'll now imagine a version of Java extended with dynamic scoping. A dynamic variable is declared with implicit. Every method has an optional implicit-specification clause, similar to a throws-specification. I will also assume this feature is somehow integrated with the generics system, and that sufficient type inference is available to not make this feature completely unwieldy.

The bash example would then translate:

public class Main {
  private static void printX(String context) implicit Object x {
    System.out.println(context + ": " + x.toString());
  }

  private static <T> void assignX(T value) implicit T x {
    x = value;
  }

  private static outer() {
    implicit String x = "a";
    printX("outer");  //=> outer: a
    assignX("b");
    printX("outer"); //=> outer: b
    inner();
    printX("outer"); //=> outer: b
  }

  private static void inner() implicit Object x {
    printX("inner"); //=> inner: b
    implicit Integer x = 1;
    printX("inner"); //=> inner: 1
    assignX(2);
    printX("inner"): //=> inner: 2
  }

  public static void main(String[] args) { outer(); }
}

As with checked exceptions, each method that calls a function with checked exceptions or with implicit variables must also declare those exceptions or implicit variables, which makes it more difficult to write generic functions. However, this is by no means impossible if the type system is sufficiently expressive. Assuming a C-style syntax for higher-order functions and generic parameters for implicit variables, we would have to express a map operation as

public static <type In, type Out, implicit... Implicits>
Out[] map(Out f(In x) implicit ...Implicits, In[] args) implicit ...Implicits {
  Out[] out = new Out[args.length];
  for (int i = 0; i < args.length; i++)
    out[i] = f(args[i);
  return out;
}

where f could require any implicit variables. This requirement is then inherited by the map function. Note that part of the problem with checked exceptions in Java is that Java does not allow a similar forwarding of the throws declaration.

Licensed under: CC-BY-SA with attribution
scroll top