4

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.

  • Dynamic typing allows a name i.e. identifier to refer to objects of different types at different points at run time -- Re-using a variable in this way is a really bad idea. Just use a different variable with it's own (static or dynamic) type. – Robert Harvey Sep 20 '16 at 18:28
  • Dynamic scoping allows a name to refer to different objects at different points in run time -- This is a bad idea too. In general, let your variables mean what they mean when you first declare them. If you need to refer to something else, create a new variable specifically for that. – Robert Harvey Sep 20 '16 at 18:30
7

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.

  • Dynamic typing and dynamic scoping do not lead to each other. Dynamic scoping is rarely seen, and even less rarely without dynamic typing. I'm really curious why you make this statement. It's clear that you can have dynamic typing without dynamic scoping but I don't see how you can have dynamic scoping without dynamic typing. Going back to your first example, the type of the variable x that function f sees depends on who called f. – Doval Sep 20 '16 at 18:49
  • @Doval with static typing, dynamic scoping would be much more verbose. Since the required dynamic variables are part of the function contract, these variables and their types would have to be declared similar to normal function parameters. The requirement to declare these means a function can not be transparent to dynamic scoping, and inherits the dynamic variable requirements of all functions it calls in turn. Sounds familar? It leads to the same mess as Java's checked exceptions. See my edit for an example of a hypothetical Implicit Variable Java. – amon Sep 20 '16 at 19:23
  • In my mind that's no longer dynamic scoping, but now I'm just splitting hairs. Scala implements something like that with its implicit parameters but the caller doesn't necessarily have to have implicit parameters. I just wanted to make sure I didn't misunderstand. Thanks. – Doval Sep 20 '16 at 19:29
  • In the first example, f would have to require one specific type for x and a program will not typecheck unless there is a x variable with correct type, or we need a generics system so that f can handle x variables of many types. – amon Sep 20 '16 at 19:29
  • 1
    "The only common programming languages I know which support dynamic scoping are Perl and Bash" – Scheme introduced lexical scoping into the Lisp world, all Lisps before Scheme and some after it are dynamically scoped: Common Lisp, Emacs Lisp, newLisp, picoLisp. Also Logo, and most Shells, including PowerShell. – Jörg W Mittag Sep 22 '16 at 21:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.