Question

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?

Was it helpful?

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.

OTHER TIPS

This is a difficult, and quite subjective issue. (And your question may get closed as opinion-based, but that doesn't mean it's a bad question - on the contrary, even thinking about such meta-language questions is a good sign - it's just not well-suited to the Q&A format of this forum.)

Here's my view of it: The point of high-level languages is to restrict what a programmer can do with the computer. This is surprising to many people, since they believe the purpose is to give users more power and achieve more. But since everything you write in Prolog, C++ or List is eventually executed as machine code, it is actually impossible to give the programmer more power than assembly language already provides.

The point of a high-level language is to help the programmer to understand the code they themselves have created better, and to make them more efficient at doing the same thing. A subroutine name is easier to remember than a hexadecimal address. An automatic argument counter is easier to use than a call sequence here you have to get the number of arguments exactly right on your own, with no help. A type system goes further and restricts the kind of arguments you can provide in a given place.

Here is where people's perception differs. Some people (I'm among them) think that as long as your password checking routine is going to expect exactly two arguments anyway, and always a string followed by a numeric id, it's useful to declare this in the code and be automatically reminded if you later forget to follow that rule. Outsourcing such small-scale book-keeping to the compiler helps free your mind for higher-level concerns and makes you better at designing and architecting your system. Therefore, type systems are a net win: they let the computer do what it's good at, and humans do what they're good at.

Others see to quite differently. They dislike being told by a compiler what to do. They dislike the extra up-front effort to decide on the type declaration and to type it. They prefer an exploratory programming style where you write actual business code without having a plan that would tell you exactly which types and arguments to use where. And for the style of programming they use, that may be quite true.

I'm oversimplifying dreadfully here, of course. Type checking is not strictly tied to explicit type declarations; there is also type inference. Programming with routines that actually do take arguments of varying types does allow quite different and very powerful things that would otherwise be impossible, it's just that a lot of people aren't attentive and consistent enough to use such leeway successfully.

In the end, the fact that such different languages are both very popular and show no signs of dying off shows you that people go about programming very differently. I think that programming language features are largely about human factors - what supports the human decision-making process better - and as long as people work very differently, the market will provide very different solutions simultaneously.

Code written using dynamic languages is not coupled to a static type system. Therefore, this lack of coupling is an advantage compared to poor/inadequate static type systems (although it may be a wash or a disadvantage compared to a great static type system).

Furthermore, for a dynamic language, a static type system doesn't have to be designed, implemented, tested, and maintained. This could make the implementation simpler compared to a language with a static type system.

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