Starting some time around 2005, under the influence of Perl, Python, Erlang, and Ruby, I became convinced that application programs should be written in dynamically-typed languages. You get it built faster, there’s less code to maintain, and the bugs are no worse. I’ve felt negative not just about statically-typed tools in general, but about the Java programming language in particular. Living in the Android world has forced me to think about this more.
The Old Argument · It’s remarkable that, fifty or so years after “Software Engineering” joined the mainstream, we have so little consensus on these issues. There are many people, including some here at Google, who think that doing large-scale software engineering without recourse to static typing is unprofessional, verging on malpractice. There are others, particularly in the community of Web builders, who think that static typing in general and Java in particular are evidence of old thinking and low skill.
Millions of words have been spent on this debate, but here are a few of the highlights as bullet points; I’ll use “dynamic” and “static” as a shorthand.
Dynamic gets systems built faster.
Static gives you a richer toolset for specifying interfaces, which encourages modularity, particularly in large systems.
Dynamic produces less code. Less code is better.
Static programmers use more advanced IDEs with strong refactoring support; this eases maintenance and reduces the burden of the extra code.
Dynamic developers have a strong unit-testing culture, partly because they lack the static-typing crutch; this supports fearless refactoring.
The Java language in particular suffers from excessive ceremony and boilerplate. Also it lacks important constructs such as closures, first-class functions, and functional-programming support.
Static software supports optimizations that allow faster performance.
Static software typically exposes shared-mutable-state threads, and experience shows that application developers have difficulty understanding and using these correctly.
Recent News · All that aside, I kept noticing that while I was working on Android apps, the fact that I was writing Java code wasn’t bothering me that much. But I couldn’t work out why.
Then, in the last few months, I’ve been working on a revision of LifeSaver, which involves Android code (in Java of course) and App Engine code, which is in Ruby (Sinatra-based, via JRuby). The contrast between the two couldn’t be sharper. And somehow I’m comfortable on both sides.
This had been rattling around in the back of my mind like a poorly packed object in the trunk of your car. Then last September a gentleman named Ricardo Bánffy, whom I haven’t met, tweeted It’s really interesting how writing Java for Android is not painful like writing Java for web apps.... Which dragged it into the spotlight where I couldn’t not think about it.
These days I’d never consider using Java for a significant Web-dev project; but it seems a comfy fit for my Android app code.
What’s the Difference? · I mean between mobile-device and Web-side programming. Let’s start with API surfaces. In a Web app, at a minimum you have to deal with:
The low-level OS interfaces: files, processes, memory, sockets, and so on.
A persistence layer; files or SQL or postrelational distributed hashes or some combination.
The Web machinery itself: HTTP and cookies and ETags and authentication.
Here’s a picture:
Now you can, if you choose, load up on tangled towers of ORM and dependency-injection abstraction and FactoryFactoryFactory joy, but these are often part of the problem not the solution, and you can do anything the Web can do without going near them.
On the mobile side, things are different. In order to use the device’s facilities fully, you have to deal with those same three basic things and a lot more besides:
More radio interfaces, potentially: WiFi, NFC, and BlueTooth.
A GPS and compass and maybe altimeter.
Audio gear, including speakers and a microphone.
A camera, with a sensor and lots of controls.
Last but not least, a vibrator.
Testing · Another observation that I think is partially but not entirely a consequence of API scale is testing difficulty. In my experience it’s pretty easy and straightforward to unit-test Web Apps. There aren’t that many APIs to mock out, and at the end of the day, these things take data in off the wire and emit other data down the wire and are thus tractable to black-box, in whole or in part.
On the other hand, I’ve found that testing mobile apps is a major pain in the ass. I think the big reason is all those APIs. Your average method in a mobile app responds to an event and twiddles APIs in the mobile framework. If you test at all completely you end up with this huge tangle of mocks that pretty soon start getting in the way of seeing what’s actually going on.
Criteria · Let’s call them the Bánffy-Bray criteria for selecting between static and dynamic type systems.
Static typing’s attractiveness is a direct function (and dynamic typing’s an inverse function) of API surface size.
Dynamic typing’s attractiveness is a direct function (and static typing’s an inverse function) of unit testing workability.
Comment feed for ongoing:
From: Phil Hagelberg (Dec 28 2011, at 12:29)
A static/dynamic discussion that doesn't mention the difference between good static languages and those lacking type inference is of dubious value IMO.
From: pb (Dec 28 2011, at 13:02)
I like the post but I think the bullet on unit-testing is going to run into trouble both because it's not super accurate and it conflicts with many of the other bullets (less code, faster, etc).
From: Matt Leidholm (Dec 28 2011, at 13:10)
While static typing is one type of formality in Java, there is no doubt that it isn't the only one, and not all of Java's problems (or its complexity or formality) can be boiled down to that one feature.
Though the idea that "static typed languages are needlessly verbose" and "dynamic typed languages are terse" is generally true, it need not be. There is no reason a value's type can't be inferred by a hypothetical static type language compiler and its usage checked at that time. Test coverage is a great thing that dynamic-typers had to learn and static-typers should pick up as well, but it is no substitute for good compile-time checking.
My Python code feels fun and pretty, but unsafe; my Java code feels verbose and ugly, but reliable. Why can't I have the best of both worlds?
From: Avdi Grimm (Dec 28 2011, at 13:17)
My reply to this turned into a riff on why web programming is slowly starting to look more and more like the systems programming I used to do: http://avdi.org/devblog/2011/12/28/systems-programming-in-the-cloud/
From: Mike Sax (Dec 28 2011, at 13:18)
Not sure if we're not mixing up cause and effect here... aren't (well-designed) APIs for dynamic languages much simpler that those for static languages? Imagine how complicated Rails would be if you'd have to use a static language.
From: Thaddee Tyl (Dec 28 2011, at 13:46)
Matt Leidholm, the best of both words is coming, and it is already there.
The thing is, the difference between static and dynamic is getting blurrier every day.
Firefox recently gained a type inference engine in their JS implementation; JS, which is dynamic, is now compiled, optimized and type inferred as if it was all implied static typing. As a result, the speed of the running program is catching up, too.
On the other hand, several static languages such as Go, Rust, and (the older) Haskell, have type inference (at varying levels). Even old C++ has (in the latest version, C++0x) the `auto` keyword which can replace `int`, `char`, etc.
From: Masklinn (Dec 28 2011, at 14:12)
Your "highlights" list is problematic, because it has less to do with static/dynamic and more to do with "Java and C#" versus pretty much everything else:
> Static gives you a richer toolset for specifying interfaces, which encourages modularity, particularly in large systems.
That's more than highly debatable, all the tools for *specifying* interfaces are there in dynamically typed languages, the difference is that they're not *enforced*.
> Dynamic produces less code. Less code is better.
Dynamic typing does not produce significantly smaller code than statically typed languages with good type systems and type inference (hell, or even bad ones, C# 4.0 code code can be impressively terse at times)
> Static programmers use more advanced IDEs with strong refactoring support; this eases maintenance and reduces the burden of the extra code.
I've yet to see Haskell or Ocaml editors with better refactoring support than RubyMine or PyCharm, let alone Smalltalk environments.
> Dynamic developers have a strong unit-testing culture, partly because they lack the static-typing crutch; this supports fearless refactoring.
> The Java language in particular suffers from excessive ceremony and boilerplate. Also it lacks important constructs such as closures, first-class functions, and functional-programming support.
As you note, that solely has to do with Java being an awful language.
> Static software supports optimizations that allow faster performance.
That's very theoretical, most of Java's performances come from its JIT, and I believe the JVM does not use static information to prime the JIT (very few JIT runtimes do).
> Static software typically exposes shared-mutable-state threads, and experience shows that application developers have difficulty understanding and using these correctly.
Again, that has absolutely nothing to do with typing discipline, most dynamically typed languages do that as well (and are correspondingly terrible tools for concurrent code).
> Let’s call them the Bánffy-Bray criteria for selecting between static and dynamic type systems.
These don't follow. In fact, they truly feel pulled out of nowhere.
And what about Objective-C? The whole "Objective" part is dynamically typed (and uses message sink nulls to boot). What about the "HTML5" umbrella APIs, which add about half the stuff you ascribe to phones on a dynamically typed language? What about people coding desktop applications in dynamically typed languages? What about the vast majority of applications actually using one, maybe two of your laundry list of "phone stuff"?
> Why can't I have the best of both worlds?
You can, but not without effort on your part.
From: Alex Cruise (Dec 28 2011, at 14:24)
Java isn't the only statically typed game in town: http://www.scala-lang.org/
From: Osvaldo Pinali Doederlein (Dec 28 2011, at 14:25)
+1 to pb; and on unit testing I see this backwards -- static typing means no time wasted in many trivial bugs or tests, so I have more time to write unit tests for stuff that really matters. BTW, I've recently read this great piece from John Carmack: http://altdevblogaday.com/2011/12/24/static-code-analysis/ -- in theory, dynamic languages can have great static analysis tools, but in practice there is no match for the stuff you get for static langs, e.g. Java's FindBugs and CodePro Analytix. A static typesystem allows these tools to have much higher precision => low rate of false positives => tool has a tolerable overhead of reviewing so developers can actually use it in a regular basis. (Notice that John comments on tools for C/C++, which are static-typed but suffer from obfuscation from the clumsy C preprocessor, irrestricted pointer manipulation, or C++'s hideously complex grammar; saner modern OO languages have no such problems so the tools are an order of magnitude easier to use).
From: Ed (Dec 28 2011, at 14:27)
> Firefox recently gained a type inference engine in their JS implementation; JS, which is dynamic, is now compiled, optimized and type inferred as if it was all implied static typing.
Not by a very, very long shot.
> As a result, the speed of the running program is catching up, too.
No, Firefox's static priming does not make code inherently go faster, what it does is reduce the performance hit of interpreting the code, before the JIT has obtained the data it needs to generate "better" code: static analysis results can be fed to the JIT's compiler to generate machine code immediately. It does not make things go faster (because there are less static type informations than there are dynamic ones in the language), it makes them go fast faster.
> On the other hand, several static languages such as Go, Rust, and (the older) Haskell, have type inference (at varying levels).
Not that it's a new feature of course, java *could* have had when first released if they'd wanted (and worked at) it: ML was first released in 1973, and the first description of Hindley-Milner dates to Hindley's 1969 "The Principal Type-Scheme of an Object in Combinatory Logic".
> Even old C++ has (in the latest version, C++0x) the `auto` keyword which can replace `int`, `char`, etc.
(ML is a decade older than C++. In fact, Caml — a dialect of ML and the ancestor to OCaml — is almost as old as C++ itself since it appeared in 1985 to C++'s 1983)
From: Mark M. (Dec 28 2011, at 15:33)
I nearly stopped reading when I got to adherents of dynamically typed languages think that static language users are unskilled. Compared to Perl, Python and worst of all, PHP? Sorry, but a language such as Java or C# is vastly more complex (and for good reason, as you summarize with more complex api's and requirements of modularity and long term reuse of an organic and complex business app.)
But your article is also out of date: C# has type inference borrowed from Haskell, and is now supporting a number of functional features, including lambdas, and more are on the way. From an implementation perspective, what are closures usually used for besides capturing state in a function language. I understand the difference, but statically typed languages have a very rich set of features for retaining state. They're called 'variables'.
From: jed (Dec 28 2011, at 16:16)
On average we have found scala to have on average 1/5th of the code to its equivalent Java in LOC and about 1/10th in bytes. It has a much richer and more powerful type system than Java, allowing for instance generalisation at the kind level (higher kinded types).
From: Juha Autero (Dec 29 2011, at 00:04)
Maybe it is just my Turbo Pascal 6.0 background, but I have always considered "static" synonymous to "compile-time" and "dynamic" to "run-time". The difference between dynamic and static typing is whether type of a variable is decided (bound) at runtime or at compile-time, not do you need to spell out types of variables explicitly or not.
If we define statically typed as "types in a statement are decided before any of the code is executed" and dynamically typed as "types in a statement are decided as the statement is executed", I wonder if you could have semi-dynamic language where types are bound after some of the code has been executed.
From: Adam Lock (Dec 29 2011, at 06:33)
Java's problem is it is not a terse language and demands a lot of boiler plate for stuff that should only take a few lines of code. Anonymous inner classes for example are heavily used but you're looking at 5+ lines of code every time even for a simple 1 line handler. I think the language would be vastly improved by stuff like type inference, auto types, closures and so on, such that it wouldn't be a big deal that there is a statically typed language underneath. And one would hope that Android / Dalvik supports these improvements.
From: Phil (Dec 29 2011, at 07:04)
It's been mentioned, but I'll mention it again: Type inference. Strong, static typing and type inference seems to be the sweet spot in language design.
From: Peter Svensson (Dec 29 2011, at 07:26)
I would like to dispute the point that only static languages have modularization (or the implication that they have).
And considering the merits of the different language type, it would be interesting to know if any statically typed language would be able to implement namespacing as a feature if it wasn't already locked in the language specification.
I think that the reason the Android Java API is simpler to use than other Java APIs (especially 'Java web' libraries) is the following;
1. The Android API describes a client. It is not generated out of the ear of the server after each user interface interaction. The 'Java web' libraries and platforms tries to shoehorn a client-server system into a single-machine model. Enter complexity.
2. Real effort have been made to avoid inane class implementations of interfaces, relying instead of static types and inner classes whenever possible.
And that's pretty much it, I think. Works wonders too :)
From: Tim Converse (Dec 29 2011, at 10:32)
"The Java language in particular suffers from excessive ceremony and boilerplate. Also it lacks important constructs such as closures, first-class functions, and functional-programming support."
This is a very concise version of the case for Scala over Java.
From: dr2chase (Dec 29 2011, at 16:16)
I'm pretty sure that the JVM, under certain circumstances, will make use of static type information in its optimizations (for suitable definitions of all the words in the previous sentence). For example, I once worked on a Brand X VM for JtPL [tm] bytecodes, and it did quite a lot with types. One of the HotSpot (C2) authors told me that they used a lattice with types and complement types in it; any trick that allows you to resolve a method into a non-virtual form is a big win.
On Java, old dogs, they can learn new tricks:
There's also static types, and static types. The Java-classic type system is pretty old and very conservative. There are other static type systems out there, and sometimes very light-weight inference will eliminate a boatload of ceremony.
On-the-other-hand, working with Python (Trac and its plugins) has been quite ingteresting; on the one hand, plugins, they just work, on the other hand, in at least one case an error that would have been trivially detected in a static type system was checked-in to the repository, had 2-3 people reporting the bug, including analysis and proposed fixes. Eventually it was fixed, but it should have taken just a few minutes before checkin.
There's "static types" and "static types".
From: Aaron (Dec 29 2011, at 19:33)
Luckily the most mundane aspect of my extensive coding career requires the least amount of time: writing code. Who knows, maybe it's that Java has forced me to put a little more thought into my problems before jumping into the code.
From: Larry Kyrala (Jan 03 2012, at 05:02)
"Dynamic developers have a strong unit-testing culture, partly because they lack the static-typing crutch; this supports fearless refactoring."
Almost right. Your statement should read "Dynamic developers [had better] have a strong unit-testing culture..."
In my experience, you are dead-on here... if you treat unit-test as "something I'll get to later" you will be forever solidly in the "static is better" camp because of endless frustration.
Now, here's another rub with being a good unit-tester in dynamic languages: you must be *excellent* in the language and have expert grasp of metaprogramming in that language. Not merely, "know a little" or "can hack a bit", but be an expert in the language to test what you want to write. Otherwise your tests and programs will be severely limited -- probably more limited than you are used to in static languages. You will know what you want to write, but won't know how to test it. Throw it away then!! You have to work every day thinking "I WILL NOT write a single line of code that is not tested" -- if you do this, you find (rather surprisingly) that your code changes and your tests change to support each other in terms of what can be tested and how you solve problems. You will also find that the resulting code morphs into the "super-ruby-ninja" code that you always see in master courses.
But if you are just "so-so" in the language, don't expect to get very far with unit-testing... you'll fail... A LOT. And the community will be somewhat snobbish and unforgiving, which makes me think that there are not many people that are good at unit-testing who know *why* they are good at unit-testing.
From: Pierre Phaneuf (Jan 18 2012, at 20:26)
The server side of LifeSaver might be reasonably simple to give a try to a Go port (which is also available on App Engine). Sounds like it should be easy enough to not be too much an imposition of time, while having the practicality of a real-life application to it, rather than a synthetic test program.