An interesting case study on the matters of scaling projects that use dynamic and interpreted language can be found in Beginning Scala by David Pollak.
I started searching for a way to express the code in my brain in a simpler, more direct way. I found Ruby and Rails. I felt liberated. Ruby allowed me to express concepts in far fewer lines of code. Rails was so much easier to use than Spring MVC, Hibernate, and the other “streamlined” Java web frameworks. With Ruby and Rails, I got to express a lot more of what was in my head in a shorter period of time. It was similar to the liberation I felt when I moved from C++ to Java...
As my Ruby and Rails projects grew beyond a few thousand lines of code and as I added team members to my projects, the challenges of dynamic languages became apparent.
We were spending more than half our coding time writing tests, and much of the productivity gains we saw were lost in test writing. Most of the tests would have been unnecessary in Java because most of them were geared toward making sure that we’d updated the callers when we refactored code by changing method names or parameter counts. Also, I found that working on teams where there were mind melds between two to four team members, things went well in Ruby, but as we tried to bring new members onto the team, the mental connections were hard to transmit to new team members.
I went looking for a new language and development environment. I was looking for a language that was as expressive as Ruby but as safe and high-performance as Java...
As you can see, major challenges in project scaling for author turned out to be in test development and knowledge transfer.
In particular, author goes into more details in explaining the differences in test writing between dynamically and statically typed languages in Chapter 7. In section "Poignantly Killing Bunnies: Dwemthy’s Stairs" author discusses Scala port of a particular Ruby example:
Why the Lucky Stiff... introduces some of Ruby’s metaprogramming concepts in Dwemthy’s Array in which a rabbit battles an array of creatures. N8han14 updated the example to work in Scala...
Compared to the Ruby code, the library parts of the Scala code were more complex. We had to do a lot of work to make sure our types were correct. We had to manually rewrite Creature’s properties in the DupMonster and the CreatureCons classes. This is more work than method_missing
. We also had to do a fair amount of work to support immutability in our Creatures and Weapons.
On the other hand, the result was much more powerful than the Ruby version. If we had to write tests for our Ruby code to test what the Scala compiler assures us of, we’d need a lot more lines of code. For example, we can be sure that our Rabbit could not wield an Axe. To get this assurance in Ruby, we’d have to write a test that makes sure that invoking |^
on a Rabbit fails. Our Scala version ensures that only the Weapons defined for a given Creature can be used by that Creature, something that would require a lot of runtime reflection in Ruby...
Reading above can make one think that as projects grow even larger, test writing might become prohibitively cumbersome. This reasoning would be wrong, as evidenced by examples of successful very large projects mentioned in this very question ("Python is successfully used for... YouTube").
Thing is, scaling of the projects isn't really straightforward. Very large, long-living projects can "afford" different test development process, with production quality test suites, professional test dev teams and other heavyweight stuff.
Youtube test suites or Java Compatibility Kit sure live a different life than tests in a small tutorial project like Dwemthy’s Array.
Original tweeter here. :)
First of all, I'm somewhat amused/shocked that my tweet is being taken so seriously! If I had known it was going to be this widely disseminated, I would have spent more than 30 seconds writing it!
Thiago Silva is correct to point out that "static" and "dynamic" more accurately describe type checking, rather than type systems. In fact, it isn't really accurate to say that a language is statically or dynamically typed, either. Rather, a language has a type system, and an implementation of that language might enforce the type system using static checking, or dynamic checking, or both, or neither (though that would not be a very appealing language implementation!).
As it happens, there are certain type systems (or features of type systems) which are more amenable to static checking, and there are certain type systems (or features of type systems) which are more amenable to dynamic checking. For example, if your language allows you to specify in the text of a program that a particular value must always be an array of integers, then it's reasonably straightforward to write a static checker to verify that property. Conversely, if your language has subtyping, and if it permits downcasting, then it's reasonably straightforward to check the validity of a downcast at runtime, but extremely difficult to do so at compile time.
What I really meant by my tweet is simply that the vast majority of language implementations perform some amount of dynamic type checking. Or, equivalently, the vast majority of languages have some features that are difficult (if not impossible) to check statically. Downcasting is one example. Other examples include arithmetic overflow, array bounds checking, and null checking. Some of these can be statically checked in some circumstances, but by and large, you'd be hard-pressed to find a language implementation that doesn't do any checking at runtime.
This is not a bad thing. It's just an observation that there are many interesting properties that we would like our languages to enforce, and that we don't really know how to check statically. And it's a reminder that distinctions like "static types" versus "dynamic types" are not nearly as clear-cut as some people would have you believe. :)
One final note: the terms "strong" and "weak" aren't really used in the programming language research community, and they don't really have a consistent meaning. In general, I've found that when someone says that a language has "strong typing" and some other language has "weak typing", they're really saying that their favorite language (the one with "strong typing") prevents them from making some mistake that the other language (the one with "weak typing") doesn't -- or conversely, that their favorite language (the one with "weak typing") allows them to do some cool thing that the other language (the one with "strong typing") does not.
Best Answer
It's sort of a myth that programmers don't have to worry about types in dynamically typed languages.
In dynamically typed languages:
You still have to know if you're working with an array, an integer, a string, a hash table, a function reference, a dictionary, an object, or whatever.
If it's an object, you have to know what class it belongs to.
Assigning one of these types to a variable or function parameter expected to be another type is almost always an error.
At a lower level, things like number of bits or signed versus unsigned frequently still must be accounted for if you are populating a TCP packet, for example.
You can run into problems where you get a zero where you really wanted an empty string. In other words, you're still debugging type mismatch bugs. The only real difference is the compiler isn't catching the errors.
I'd argue that you aren't even saving much typing- , because you tend to want to document in comments what type your function parameters are instead of documenting it in your code. This is why doxygen-style comment blocks are much more popular in practice throughout dynamically typed code, where in statically typed languages you mostly only see them for libraries.
That's not to say that programming in dynamically typed languages doesn't feel more pleasant because the compiler isn't always on your back, and experienced programmers don't tend to have difficulty finding and correcting the kind of bugs that static typing would catch anyway, but that's a completely separate issue from an alleged increase in efficiency or reduction in bug rate, for which dynamic typing is at best even with static typing.