68 comments

[ 2.9 ms ] story [ 48.5 ms ] thread
The author's stated reasons for why dynamic languages are used may be valid (I use them because the type of job I like to get usually does) but there are real advantages to them.

It is much easier to quickly prototype or experiment in a dynamic language. Note that I am thinking versus a language like Scala or Haskell where types are a integral part of the language. This applies less to languages like Java. You don't have to stop and figure out your types for every function and then go back and change them every time you figure out there is a better way to represent the data.

That being said, I get their 'Dynamic Languages really just have one type' argument. I understand what they are saying. Doesn't mean we can't still use the term dynamic to describe such languages. Words mean whatever we(as a group) want them to mean.

I think the author of the article is unreasonably upset about dynamic languages. It is, ironically, restrictive to only use the most static of languages. Dynamic languages are fun to!
10,000 lines of bubblegum and chicken wire is not fun. At all.
I never said you should be building 10,000 line apps in python, nor passed any value judgment. All I was implying is that sometimes it is just fun to use different sorts of languages.
I can't imagine how you are deriving an emotional state from that post.
"It is much easier to quickly prototype or experiment in a dynamic language."

I disagree. I can say what I mean with types, and if I didn't mean what I said, refactoring is a breeze.

"You don't have to stop and figure out your types for every function and then go back and change them every time you figure out there is a better way to represent the data."

You don't have to do this in languages like Haskell either. Type inference will help you most of the time when figuring out a function's type, and you don't have to use the type system as it was intended at first. You can have a prototype with Strings and Lists, the compiler won't be able to help you much though.

While Haskel has quite a few tools to help assuage issues like this, I can't help but think that niche tools do not define the genre. When I hear "statically typed", I don't think of Haskel, I think of C, C++, Go, Java, and Rust.

All of these languages offer tools to handle similar duck typing via interfaces or traits or polymorphism, but they are far from simple to use.

When Haskel gains a mindshare outside of its current niche, or the type inference becomes more broadly implemented in languages used across our industry, then let's talk about how it makes statically typed languages better and easier to use than "unityped" languages.

> I can say what I mean with types

This is one of those things that is perhaps true in theory, but often falls short in practice. Static type systems tend to require (more or less frequent) additional incantations, but may not be sufficiently-express, even with those incantations, to express the desired intent.

(Or, the required complexity of the incantations may be a greater cognitive workload than actually writing the functional code.)

> You don't have to do this in languages like Haskell either.

Most static languages -- particularly, the ones with the strongest standard libraries and ecosystems that are likely to support whatever it is you are doing -- aren't like Haskell.

> You don't have to stop and figure out your types for every function and then go back and change them every time you figure out there is a better way to represent the data.

Hmm, if you change your data representation from an array to a dict, say, don't you anyway need to go back and change code in every function that accesses this data?

Frequently the answer is no, you don't need to change every function. If you're iterating through values, are obtaining the lookup value outside the function, or just passing it along, no changes are necessary.

Compared with your average statically typed language, you do have to update the function signature and everything that calls it.

Now then, admittedly, Haskel is not your average statically typed language - but then again it's very rare to actually run up against Haskel in production code.

> iterating through values

I don't think you can iterate thought the values in an array and in a dict in e.g. python with the exactly same syntax.

> just passing it along

In languages with global type inference (e.g. OCaml) you would not have written the type in the code in the first place, so changing the type of an argument that you just pass through, would not require any changes in the code.

But yes, in Haskell people are in the habit of writing the type signatures, even though the compiler does not require them, so in this case edits are needed.

> I don't think you can iterate thought the values in an array and in a dict in e.g. python with the exactly same syntax.

That depends on what you mean by "values". The default iterator over dictionaries in python iterates over the keys, so if you use the exact same syntax as iterating over members of a list you iterate over the keys of a dictionary, not what are usually called the values (there is a separate iterate for that, and for key/value pairs). But, yes, dicts and lists are iterable via the same syntax since they both support Pythons iteration protocol.

Type inference in static languages eliminate 90% of the advantages of dynamic languages including being easier to quickly prototype or experiment.
While the core theory of this article, that dynamically typed languages are just statically typed languages with just one type, is correct, its extensions are questionable at best. It tries to claim that it is somehow restrictive to have only one type. That is nonsense, anyone has sat down and tried to write code in both Python and Haskell can tell you it is much harder to get the Haskell compiler to accept your code. Now having such a strict compiler is great sometimes, and having a more forgiving compiler like Python's is also great soemtimes. But that observable forgiveness makes it obvious why it is silly to claim Python programmers are struggling against "the bondage of restricting attention to a single type."
Also Python seems to look like a dynamic language, but it isn't. The type system is really not as easy as the most other interpreted languages.
Don't confuse static/dynamic with strong/weak. Python is strongly typed, but it's still dynamic.
FYI: None of those terms actually mean anything.

Or, more accurately, they mean different things to different people.

There's unfortunately very little in the way of standardized vocabulary for discussing this topic in a meaningful way. My theory is that this reflects the religious nature of the discussion.

You can be both dynamic and strong
When Bob says that it's restrictive, what he means is simply that you cannot say more. Which is true -- you can't express things when your only type is "something". You're allowed to saying nothing, and that's it. And that's pretty darn restrictive!
I take it oppositely.

In C++, if I declare the type of something incorrectly, the compiler yells at me. That's restrictive. I have to get it "right".

Python doesn't ask me, because it doesn't care, so I can't be wrong. That's freeing.

You have to get it right in Python too, and you can certainly be wrong — the information is just implicit and the compiler won't check it for you. You find out whether you're wrong when your program either behaves correctly or doesn't. A lot of the errors I've found debugging Python code have boiled down to "This code wasn't expecting that type."
C++ is probably in that land of bad languages the author was referring to.
As CHC says below, "dynamic" typing is no different. It's just a deferment of wrongth, not a lack of it.

But if I were to take a staunch SPJian-McBridian position, I would say this attitude arises from really useless type systems.

A good type system is EITHER capable of letting you say as little as necessary, and only telling you when something is messed up (like Haskell or ML, which have type inference and therefore don't require you to write types) OR is capable of letting you say what you mean, and then doing a lot of work for you.

This is the wonderful benefit of dependent types: you can say enough about your intention that you stop fighting the type checker, and actually get the type checker to do work for you.

This is really something that hasn't percolated into the mainstream of programming yet, but is slowing creeping into Haskell with typed holes, but in dependently typed languages like Agda or Epigram, types can be expressive enough that your editor can write half the program for you just because the type you wrote has only one program as its inhabitant.

McBride likes to describe it as, Dependent Types warp code space so that the direction we want to travel is "down", and all we have to do is let ourselves fall.

As an example, here's a demonstration of some simple (but exemplary) interaction with Agda. If we define this type:

    data Vec (A : Set) : Nat -> Set where
      Nil : Vec A 0
      Cons : forall {n} -> A -> Vec A n -> Vec A (1 + n)
where `Nat` is the type of natural numbers and `Vec` is the type of lists indexed by their length, then we might be inclined to write this function:

    append : forall {A m n} -> Vec A m -> Vec A n -> Vec A (m + n)
    append xs ys = {! !}0
where the `{! !}0` is a portion of the program that hasn't yet been defined, called a hole. We can put the cursor in the hole (hole 0) and ask the editor to do a case split on `xs` with `Control-c Control-c x`, and the program will change to be

    append : forall {A m n} -> Vec A m -> Vec A n -> Vec A (m + n)
    append Nil ys = {! !}0
    append (Cons x xs) ys = {! !}1
if we put the cursor in hole 0, we can hit `Control-c Control-a` (a for agsy, or auto) and itll fill in the hole with the solution `ys`, and if we do the same in hole 1, itll fill in `Cons x (append xs ys)`.

The type of the function we're defining is enough that agda can actually find the definition with almost no help from us.

Not knuckle wrapping here :)

This article feels a bit condescending. Like most things, it's a trade-off. While sophisticated types do have a lot of advantages over dynamically typed languages (I spend most of my time using languages like that), that doesn't mean there's no trade-off.

Dynamically typed languages are, in my opinion:

* Simple

* Typically require less design upfront

* Easier to get started with

There's real value in that. Perhaps not for the author. It's easy to scoff at languages like ruby, python or javascript and claim they're completely inferior to 'real languages', but there are completely valid reasons to use them. The real value comes from simplicity.

If you use a language where runtime/dynamic typing is a subset of what's possible, you lose that simplicity.

On the other hand, you also lose some simplicity if you use a language where the contents of a given variable could take on literally hundreds of different forms — some of which you weren't even aware of when you wrote the code — and could change form unpredictably at any time. There is a lot of complexity inherent in dynamism.
The trick is, most of the time it doesn't matter. So long as it quacks like a duck, and we need the value to quack like a duck, we don't really care that it's a wolf with a duck call.
But most of the types in your program probably do not quack like ducks. Even the ones that seem duck-like enough that somebody might deliberately pass them into your function sometimes turn out to actually honk like a goose (e.g. bytes vs. str in Python, or unicode vs. str in other versions of Python).

Having used mostly dynamic languages throughout my career, personally, a huge number of the errors I've encountered have boiled down to getting the wrong type of thing — an unexpected null, a Business where I expected a Person, an array where I expected a string, an escaped HTML string where I expected an unescaped one, etc. Sometimes they quack kind of like a duck, but still do it differently enough to trigger faulty behavior (e.g. people and businesses both have names and addresses, but the latter can be in multiple places at the same time).

It usually works right, but in the cases where you're passing sensible types, you'll probably be OK in a statically typed language too — not always, but usually. Static type systems introduce some edge cases where you need to think about the type system even though you shouldn't have to, and dynamic type systems introduce other edge cases where your program's behavior can be unexpectedly brittle. They're both kinds of unnecessary complexity.

Exactly, another use is prototyping value. A language in which you can hammer a prototype out without thinking about or solidifying all the exact details -- it's more about trying out the concept, after all -- is infinitely easier to prototype with. After testing if the concept works, move to a more rigourous type system so you can bulletproof it against possible errors, etc.
While I completely agree, the sheer amount of "proto-duction" projects that are floating around and that I've had to try and maintain over the years makes an interesting argument for statically typed languages, I think!
"Typically require less design upfront"

I disagree with this (possibly depending on your particular value of "require"). The assistance that static typing can give in refactoring means I don't have to be nearly as confident in my initial design. Without the types, it's a lot more important that I get everything right initially. I find this to be true even when prototyping.

This is not exactly the point the article talks about, but it's irked me for quite some time. When you see the documentation for a library in a dynamic language, it looks the same as if the language was static: the class of all the arguments and what the functions return is specified. So in the end you end up using it just like you would a static language, but with all the type checks done at runtime instead of before.

This doesn't make much sense if you want to avoid errors.

Sure, that's the common argument, but static languages often require a bunch of gymnastics to get the compiler to accept valid code. They have a lot of abstractions that are not even necessary in dynamic languages.
The gymnastics part is correct. You have to convince the compiler the code you are producing is correct, even if it may be correct already. However, this makes your code way more unlikely to crash or be incorrect.

The "lot of abstractions" is the distinction between type and class the article addresses, I guess?

> static languages often require a bunch of gymnastics to get the compiler to accept valid code

I'd rather do the gymnastic and have the compiler tell me where I screw up right away. That's a tighter, more accurate feedback loop than a REPL. (Source: trying to implement a simple depth first search in both Lua and Ocaml.)

And of course, you can still have the repl :D
> Sure, that's the common argument, but static languages often require a bunch of gymnastics to get the compiler to accept valid code.

There's really two problems hidden in that that deserve to be considered:

(1) Static languages with insufficiently-expressive type systems may require more complex code (in terms of actual operations, not just type declarations) for the code to be valid when compared to dynamic languages (or static languages with sufficiently-expressive type systems.), and

(2) static languages may, depending on the situation and the completeness of their type inference system, require arcane incantations to the type system before it accepts that correct code is, in fact, correctly typed.

Some statically-typed languages are good on one or both of these measures, and so make less of one or both types of problems (Haskell, IMO, is pretty good on both, as static languages go, but lots of more popular static languages are really bad at one or both.)

That's an overly broad generalization, and doesn't line up with my experience.

For example I don't see much about "classes" in MDN:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

https://developer.mozilla.org/en-US/docs/Web/API/Window/loca...

"Window.location returns a Location object" "you can also assign a DOMString to it" "The parseInt() function parses a string argument and returns an integer" "If string is not a string, then it is converted to one"

Seems like this documentation talks about little else than types to me?

> "The parseInt() function parses a string argument and returns an integer"

This seems... factually incorrect? Shouldn't it say "a double"? (or a "number", which just means "double" in JS)

It might ignore any characters after a ".", but it's impossible to return an integer in JS...

No, it returns an integer. ECMAScript doesn't officially have a distinct integer type, but the number returned will still be an integer. Incidentally, most JavaScript implementations actually do have an internal integer representation, even though typeof will still just say it's a number.
string: The value to parse. If string is not a string, then it is converted to one [...]

radix: An integer between [...]

The Window.location read-only property returns a Location object

You gave supporting evidence for my point.

Take PHP or Python's standard library. Both are built around types, yet this is legal:

   $foo = "hello, world!";
   $foo = 12;
When you use a string-related function in Python, you expect the parameters to be strings, yet there's no type-safety enforced until runtime.
This is legal in Haskell:

    let foo = "hello, world"
     in let foo = 12
         in foo

Whether bindings can be shadowed has nothing to do with whether typing is static or dynamic.
(comment deleted)
"So in the end you end up using it just like you would a static language, but with all the type checks done at runtime instead of before" When I first worked on a JavaScript project I found that there were explicit unit tests that just checked for types. I wasn't expecting that. You have to write more code than in a statically typed codebase. Dynamically typed languages are not for lazy programmers!
For me the big difference between dynamic and statically typed languages is catching bugs like:

    foo = bar()
    if foo:
      fooo = baz()
in a static language, you'd have to write int foo = and the third line where you have an assignment fooo would be caught by the compiler. Of course there's advantages to dynamic languages, like being able to write something like

    mydata = {}
    mydata['foo'] = {}
    mydata['foo']['bar'] = MyObject()
without a lot of boilerplate
thanks to tab completion i virtually never run into this bug in javascript or ruby
could you share what editor and what, if any, plugins you're using?
sublime text 3, out of the box.

the only plugin i have is ES6/Babel syntax highlighting.

it mostly "just works"

That's what linting is good for, at least in JavaScript where you have optional "var" declarations: You can set jshint to flag as an error any assignment that isn't part of a "var" declaration (or any reference that isn't explicitly defined), which will catch (at compile time) any variable name typos.

It won't catch this.fooo, though, so it's only a partial solution. My editor has great completion, though, so as long as the string is IN the file somewhere, I usually autocomplete it. Way fewer typos that way.

I see there's a PyLint, so MAYBE it has a way of spotting typos like the one you cite? Not sure how it would work in Python, though, other than on variable reads.

Certianly lint handles this. Some lint checks are quite reasonably considered type constraints, even if they historically have been implemented seperately. In particular, uninitialized values and unused variables seem very much in the same theoretical space as linear types.
That's not really a difference between dynamic and statically typed languages — what makes the difference here is having different syntax for variable declaration and assignment. It so happens that most dynamic languages use the same syntax for both, but there's nothing about separating the two that requires static typing. For example, strict mode JavaScript is a dynamic language that can detect this error, because JavaScript uses var to declare variables.
Static typing has no relation to explicit declaration of variables.
In the first case, any sane static language will use type inference, so you won't have to write out "int foo", just "let foo" or equivalent. (I'd also argue that any sane static language would disallow or at least heavily discourage mutable variables, but that's neither here nor here).

The second case is easily handled by static languages, for example, in Swift:

    var mydata = ["foo": Dictionary<String, MyObject>()]
    mydata["foo"]["bar"] = MyObject()
This is only longer than it needs to be because of using two lines, you wouldn't need the explicit declaration to do it in one:

    let mydata = ["foo": ["bar": MyObject()]]
This also gets rid of mutable state!
I don't think your example has much to do with typing. The types of foo and fooo can be inferred from the return types of bar and baz, so in a language that supports type inference (e.g. Scala), no annotations are necessary.

As for whether "fooo" is a valid symbol, whether you can declare a variable without a keyword is unrelated to the type system. See, e.g., Go, which has different operators for declaration-with-assignment and reassignment (:= vs. =), but this is merely a syntactical safety net, not an outright necessity.

You can apply this same idea to serialization formats. JSON doesn't have a schema. On the other hand, you can think of all JSON values as belonging to this Protocol Buffers schema:

    message JsonArray {
      repeated JsonValue value = 1;
    }

    message JsonObject {
      map<string, JsonValue> properties = 1;
    }

    message JsonValue {
      oneof value {
        JsonObject object_value = 1;
        JsonArray array_value = 2;
        bool is_null = 3;
        bool boolean_value = 4;
        string string_value = 5;

        // Represented as a string because JSON doesn't restrict the
        // range/precision of numbers.
        string number_value = 6;
      }
    }
So, this is something I've been thinking about recently: do you gain much by using Protocol Buffers (et al) for a client-side web application's communication with a server? Having JSON/XMLHttpRequest in the browser by default is such a boon that it'd have to be a lot better as a serialisation format for it to be worth it, but that might actually be the case?
(comment deleted)
Having gone back and forth between statically and dynamically typed languages a few times now, I have to say that this comment sums it up for me: "expressing and enforcing invariants in the program itself is so helpful that it’s just ludicrous to deprive yourself of them." It's the single biggest problem I have when I work in a "unityped" language.
I've gone through the same transformation but I think you aren't appreciating the larger picture. Having the ability to specify both compile time and runtime invariants are both useful features. Java is good at expressing invariants around datatypes that looks like hardware data storage but it is so restrictive around interfaces that it is easy to paint yourself in a corner. Since Java was the mainstream typed language, people saw what could be done in languages like Python without such enforced rails, that many people decided the expressiveness was worth the tradeoff. I think Java 8 is much better about the tradeoff, but ultimately I think we haven't invented the system for expressing invariants in a language that makes the best tradeoffs between expressiveness and flexibility.
I like the fact that interpreting subjectively what 'Dynamic' and 'Static' means for a language, the author is able to pull a rabbit out of his proverbial hat ... except that we see the hole beneath the hat, and the rabbit is a dead rat. Clearly programming in Python vs C++ is a world apart in terms of how 'fast' you can get simple things up and running.
The author specifically says C++ is too restricted, and that it makes no distinction between the "class" and "type" the author is talking about.

C++ has no sum types (afaik, the language is so huge I might very well be wrong)

C++ (like C) has union, with which you can build tagged unions (aka sum types).
Be that as it may, the tone of the article is clearly written 'looking down' at the readers, and tries to rack up some points by showing 'how smart I am (author) to be able to correct most of you...'. If he simply described that the definition of the terms, with some examples and then lead into his point to correct the 'popular' assumptions, it would have been a lot better than feeling like you're being lectured from the pulpit on a Sunday morning.
The last line of the article: "Let a thousand flowers bloom - as long as they're all static."

More to the point, things that are formally equal to each other do not necessarily have equal value in certain uses. (For example, think about Stokes' Theorem. It says that an integral of a function on the boundary of a manifold is equal to the integral of the derivative of the function on the whole manifold. That's useful precisely because sometimes the integrals, while they have equal results, are not equally hard to do.)

A dynamically typed language may be formally equivalent to a static unityping, interpreting the tag at runtime. That doesn't mean that they're equally easy to use, though. When I want to do that kind of thing, I want to do it like it's dynamic, not where I have to do the book-keeping to make it dynamic.