Software Engineering
languages dynamic-typing static-typing
Updated Sat, 10 Sep 2022 17:33:27 GMT

Is there a real advantage to dynamic languages?


First I want to say Java is the only language I ever used, so please excuse my ignorance on this subject.

Dynamically typed languages allow you to put any value in any variable. So for example you could write the following function (psuedocode):

void makeItBark(dog){
    dog.bark();
}

And you can pass inside it whatever value. As long as the value has a bark() method, the code will run. Otherwise, a runtime exception or something similar is thrown. (Please correct me if I'm wrong about this).

Seemingly, this gives you flexibility.

However, I did some reading on dynamic languages, and what people say is that when designing or writing code in a dynamic language, you think about types and take them into account, just as much as you would in a statically typed language.

So for example when writing the makeItBark() function, you intent for it to only accept 'things that can bark', and you still need to make sure you only pass these kinds of things into it. The only difference is that now the compiler won't tell you when you made a mistake.

Sure, there is one advantage to this approach which is that in static languages, to achieve the 'this function accepts anything that can bark', you'd need to implement an explicit Barker interface. Still, this seems like a minor advantage.

Am I missing something? What am I actually gaining by using a dynamically typed language?




Solution

Dynamically-typed languages are uni-typed

Comparing type systems, there's no advantage in dynamic typing. Dynamic typing is a special case of static typing - it's a statically-typed language where every variable has the same type. You could achieve the same thing in Java (minus conciseness) by making every variable be of type Object, and having "object" values be of type Map<String, Object>:

void makeItBark(Object dog) {
    Map<String, Object> dogMap = (Map<String, Object>) dog;
    Runnable bark = (Runnable) dogMap.get("bark");
    bark.run();
}

So, even without reflection, you can achieve the same effect in just about any statically-typed language, syntactic convenience aside. You're not getting any additional expressive power; on the contrary, you have less expressive power because in a dynamically typed language, you're denied the ability to restrict variables to certain types.

Making a duck bark in a statically-typed language

Moreover, a good statically-typed language will allow you to write code that works with any type that has a bark operation. In Haskell, this is a type class:

class Barkable a where
    bark :: a -> unit

This expresses the constraint that for some type a to be considered Barkable, there must exist a bark function that takes a value of that type and returns nothing.

You can then write generic functions in terms of the Barkable constraint:

makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)

This says that makeItBark will work for any type satisfying Barkable's requirements. This might seem similar to an interface in Java or C# but it has one big advantage - types don't have to specify up front which type classes they satisfy. I can say that type Duck is Barkable at any time, even if Duck is a third party type I didn't write. In fact, it doesn't matter that the writer of Duck didn't write a bark function - I can provide it after-the-fact when I tell the language that Duck satisfies Barkable:

instance Barkable Duck where
    bark d = quack (punch (d))
makeItBark (aDuck)

This says that Ducks can bark, and their bark function is implemented by punching the duck before making it quack. With that out of the way, we can call makeItBark on ducks.

Standard ML and OCaml are even more flexible in that you can satisfy the same type class in more than one way. In these languages I can say that integers can be ordered using the conventional ordering and then turn around and say they're also orderable by divisibility (e.g. 10 > 5 because 10 is divisible by 5). In Haskell you can only instantiate a type class once. (This allows Haskell to automatically know that it's ok to call bark on a duck; in SML or OCaml you have to be explicit about which bark function you want, because there might be more than one.)

Conciseness

Of course, there's syntactical differences. The Python code you presented is far more concise than the Java equivalent I wrote. In practice, that conciseness is a big part of the allure of dynamically-typed languages. But type inference allows you to write code that's just as concise in statically-typed languages, by relieving you of having to explicitly write the types of every variable. A statically-typed language can also provide native support for dynamic typing, removing the verbosity of all the casting and map manipulations (e.g. C#'s dynamic).

Correct but ill-typed programs

To be fair, static typing necessarily rules out some programs that are technically correct even though the type checker can't verify it. For example:

if this_variable_is_always_true:
    return "some string"
else:
    return 6

Most statically-typed languages would reject this if statement, even though the else branch will never occur. In practice it seems no one makes use of this type of code - anything too clever for the type checker will probably make future maintainers of your code curse you and your next of kin. Case in point, someone successfully translated 4 open source Python projects into Haskell which means they weren't doing anything that a good statically-typed language couldn't compile. What's more, the compiler found a couple of type-related bugs that the unit tests weren't catching.

The strongest argument I've seen for dynamic typing is Lisp's macros, since they allow you to arbitrarily extend the language's syntax. However, Typed Racket is a statically-typed dialect of Lisp that has macros, so it seems static typing and macros are not mutually exclusive, though perhaps harder to implement simultaneously.

Apples and Oranges

Finally, don't forget that there's bigger differences in languages than just their type system. Prior to Java 8, doing any kind of functional programming in Java was practically impossible; a simple lambda would require 4 lines of boilerplate anonymous class code. Java also has no support for collection literals (e.g. [1, 2, 3]). There can also be differences in the quality and availability of tooling (IDEs, debuggers), libraries, and community support. When someone claimed to be more productive in Python or Ruby than Java, that feature disparity needs to be taken into account. There's a difference between comparing languages with all batteries included, language cores and type systems.





Comments (5)

  • +1 – You don't necessarily need a dedicated for for each type of array; just like makeItBark works for any type with a bark function, for can be made to work with any type that has an indexing function or a function for retrieving an iterator. Yes, each type needs to implement its own indexing function but this is no different in a dynamically-typed language. OCaml's use of + is .+ is related to resolving ambiguity with type inference. Consider fun (a, b) => a + b. If they had overloaded +, the language wouldn't know whether the arguments are ints or floating point... — Jul 03, 2014 at 13:41  
  • +2 – You forgot to attribute your source for the first paragraph -- existentialtype.wordpress.com/2011/03/19/… — Jul 03, 2014 at 13:51  
  • +3 – @Matt Re: 1, I haven't assumed it's not important; I addressed it under Conciseness. Re: 2, although I never explicitly said it, by "good" I mean "has thorough type inference" and "has a module system that allows you to match code to type signatures after the fact", not up-front like Java/C#'s interfaces. Re 3, the burden of proof is on you to explain to me how given two languages with equivalent syntax and features, one dynamically-typed and the other with full type inference, you wouldn't be able to write code of equal length in both. — Jul 03, 2014 at 14:11  
  • +4 – @MattFenwick I've already justified it - given two languages with the same features, one dynamically-typed and the other statically-typed, the main difference between them will be the presence of type annotations, and type inference takes that away. Any other differences in syntax are superficial, and any differences in features turns the comparison into apples vs oranges. It's on you to show how this logic is wrong. — Jul 03, 2014 at 14:29  
  • +1 – You should have a look at Boo. It's statically typed with type inference, and has macros that allow for the language's syntax to be extended. — Jul 03, 2014 at 14:36