Superstable Blog

What is a dynamic language, anyway?

John Gruber has compiled a collection of links to discussions about the use of dynamic scripting languages for desktop application development. One of the sweeping generalizations made by nearly everybody involved in the discussion is the concept of a “dynamic language.” In his article The Planks in the Bridge, Jesper asserts that Ruby and Python are more dynamic than Objective-C while Java is less dynamic. Unfortunately, it’s not entirely clear which features separate dynamic languages like Ruby and Python from less dynamic languages like Java.

Ruby and Python are interpreted, dynamically typed, and reflexive. A powerful combination it seems, but which features are the ones that make the languages powerful? What makes it easy to express certain ideas in some languages and hard to express them in others?

INTERPRETED LANGUAGES

The fact that Ruby and Python are referred to as interpreted languages, rather than compiled languages is just a red herring. The trade-off between interpreted and compiled languages is that of platform agnosticism vs. native speed. Programming languages form a continuum between these endpoints, with bytecode-interpreted languages like Java somewhere in the middle. Just-in-time compiling further blurs the distinction between interpreted and compiled. The fact that so-called dynamic languages fall in various places on the continuum shows that the choice compilation or interpretation has no bearing on what the language is capable of expressing. As it turns out, Objective-C – a compiled language – is more dynamic than Java, which is executed as bytecode in a virtual machine. Python, which is bytecode interpreted, and Ruby, which is interpreted, are both more dynamic than Java.

The advantage of an interpreted language is not manifested in language expressiveness, but in development efficiency. The power comes from reducing the code-compile-run-debug development cycle to code-interpret-debug. The downside, of course, is that interpreted code very often is slower. As the performance of virtual machines, runtime environments and interpreters improves, the distinction between interpreted and compiled languages will continue to blur. The bottom line however, is that interpretation as opposed to compilation doesn’t make a language dynamic.

DYNAMIC TYPING

It’s extremely difficult to shed light on all the subtleties of type systems, especially because the terminology is not widely agreed upon. Programming language researcher Benjamin C. Pierce had an amazingly difficult time trying to sort out the meaning of type system terminologies and eventually had to conclude that “the usage of these terms is so various as to render them almost useless.”

In order to communicate effectively on this topic, I will attempt to avoid the obviously controversial terms in the taxonomy and carefully define the terms I do use. Let’s consider the declaration int number = 5, in which the variable declaration requires the identification of the variable’s type, an example of explicit typing. The opposite is an implicitly typed language, where the type of a variable is determined by the type of the value assigned to it. The term statically typed describes languages that require a variable to have only one type throughout the execution of a program, and dynamically typed to refer to languages where a variable may change type during execution. These two concepts are orthogonal; a type system can be explicit or implicit without any impact on whether it is static or dynamic.

There is some merit to the argument that dynamically typed languages are more flexible. Dynamic typing indeed provides more flexibility to the programmer, but it does not add any expressive power to the language. By that I mean that being able to re-use variable names is not a powerful feature. I have never seen a program in a dynamically typed language use variables in a way that couldn’t be written in a statically typed language simply by adding a few additional variables.

I imagine some people see explicit typing as programmatic bureaucracy, where each variable declaration requires an extra bit of paperwork to be submitted to the compiler or interpreter. I understand this sentiment to a degree, because explicit type declarations are technically unnecessary. A compiler can easily look at a program, deduce the datatype assigned to each variable and check for illegal operations even when datatypes are not explicitly defined in the program. In fact, ML, which is type-checked at compile time, uses type inference to deduce the datatype of the eventual evaluation of an expression, and does not require explicit datatype declarations.

However, it’s not always possible to determine the datatype of every single operation before the program executes. Programmers use typecast operations to reassure the compiler that, “yes, I know what I wrote may be unsafe, but trust me, it will work.” Most often, this sort of override of the type system can only be typechecked at runtime, which is why languages like Java have ClassCastExceptions. This distinction is unnecessary when discussing interpreted languages since such languages don’t have a separate compile time and runtime. All typechecking in these languages occurs during program execution.

But do these type system features make the language dynamic? My response is a resounding “No.” The details of when a program is type checked has very little to do with what a programmer can express in the language. Similarly, changing the type of a variable during execution or omitting a type declaration may make development easier, but the lack of dynamic and implicit type checking does not make certain programs impossible to express. Dynamic typing and implicit typing are done more out of convenience than for any expressive power they afford, and most arguments about the relative merits of either paradigm are really statements of personal preference.

REFLECTION

Reflection is the property of a language that gives programs the ability to observe and modify their own structure during execution. This set of features is borrowed from functional languages, especially LISP-based languages where program and data are one and the same. The typical set of reflective features are higher-order functions, introspection and runtime alteration of the object structure.

Higher-order functions are functions that (a) take one or more functions as input or (b) output a function. These sorts of functions are extremely powerful for creating reusable modules. For example, some languages have a map() function that applies a function to each element of an array. Additionally, some list implementations include a sort() function that takes a compare() function as an argument to sort the list based on the comparison.

Introspection is a fairly straightforward feature of dynamic languages. Essentially, it is the ability for a program to observe its own structure. On the most basic level, for example, Java allows you to get the runtime class of an object by calling the getClass() method. From the object’s class, you can get any of its declared fields, constructors, methods, superclasses and interfaces. And of course, each Field, Constructor, Method or Class is an actual first-class object. Using introspection, you can create new instances of objects, modify values of declared fields and invoke methods on instances. Java’s limitation, however, is that you cannot modify class objects at runtime.

Run-time alteration of objects allows the programmer to modify the structure and behavior of objects programmatically. While Java does not allow you to add new methods or fields to objects, many more dynamic languages do. In fact it is this property of languages that make technologies like Cocoa bindings possible.

Jesper mentions Cocoa bindings in his article as an example of a feature that requires a dynamic language. Bindings take advantage of a mechanism called Key-Value Coding to notify observing objects when the value for a particular key is changed. To implement Key-Value Observing, you need to be able to modify the observed object at runtime, which Java plain doesn’t allow. KVO is possible in Objective-C through a technique called isa-swizzling, where an intermediate class is inserted to catch and respond to messages, forwarding them to the observed class.

Isa-swizzling is an extra-linguistic feature: there is no Objective-C syntax to insert intermediate objects. It essentially uses the low-level nature of C as a back door into the inner workings of Objective-C, which is ultimately just an object-oriented facade. There are languages that are more dynamic than Objective-C that provide syntax for exactly these types of manipulations, and they are these features that make dynamic languages powerful.

LANGUAGE DYNAMICS

Perhaps it is no coincidence that the word dynamic comes from the Greek word for power. The reflexive features that make a language dynamic also make it powerful. The idea that the object structure and control structure of a program can be defined once and not change is outdated. The languages that allow people to write flexible programs are the ones that have real power, and they are the languages that will gain momentum as more programmers discover the power they have been given.

Comments are closed.