94 comments

[ 3.0 ms ] story [ 173 ms ] thread
I agree with the article. No silver bullets, just lots of regular bullets, and static typing is just another bullet.

The main advantage for static typing from my experience are what happens when you have projects above 10kloc with multiple maintainers, and maybe the original devs have moved on to other teams. At that stage in a product's lifecycle, static typing reduces friction and covers some cases that are hard to test properly. Greenfield projects don't suffer from this problems for the first year or so.

Refactoring is the major thing that's easier with static typing, IMO.

All tooling is much easier with static typing. Code completion, highlighting mistakes as you type... features that are much more powerful in a statically typed world.

I agree though: it's a tool in the tool belt. It also varies dramatically across different languages and supported features which can make a huge difference in the kinds of problems it solves for you. Lately I've been doing a lot more modeling of problems with types when it makes sense... things like measurement units and their operators to ensure you can't accidentally add meters to watts. Or using unique ID types for each model so you can't accidentally create a reference with the wrong ID.

The newer languages and updated standards (e.g. Swift, Rust, C++ with auto) take a lot of the useless repetition of types out of the equation and feel more like type annotations in a dynamic language. In the end I think we'll mostly settle on hybrids. Some will be static by default but support dynamism. Others will be dynamic by default but support static type annotations.

What won't change is that you can't scale transistors below a single atom and we can see that endpoint on the roadmap. Performance improvements will not continue at the current rate. One could argue we're already there given the perf delta between CPU and memory or CPU and storage has gotten orders of magnitude worse.

In a future where abstractions impose a real cost the optimization function may change, especially for established businesses and processes. Hiring a couple of Rust or Swift senior devs to rewrite your Python backend in a statically compiled language, then applying repeated optimizations might be worth the cost when you know it will be 10 years before a new CPU is released and it will be 10-20% faster at best. Or if you imagine future CPUs require exotic materials and 10x as much R&D to hit the same targets they become 10-100x as expensive.

I've started to feel the same way. I feel like a lot of developers would be better off in a dynamically-typed language because it is such common practice to completely subvert the type system and there are such strong resources available for improving testing practices.

My last job was in C#, and it seemed like so many things were stringly typed, entirely nullable, or used something else to weaken the type system.

In addition to this failure to use the type systems, testing is becoming a much more rich environment. I think popular resources on effective testing are much better and numerous than popular resources on effective typing. There are great books and tools around helping developers test their software more thoroughly and efficiently. Comparing these testing resources with typing resources, the closest popular book I can think of for typing is Design Patterns, and I don't think most popular type systems are built with the program correctness as a top priority.

I think rich type systems like haskell's will play more of a role in the future, and I think current users of these systems do experience a benefit. But if you have only ever done dynamically typed programming or used Java/C#, you are probably going to improve your correctness more over the next year by using a property based testing tool like propr (ruby), hypothesis (python), fscheck (.net), scalacheck (jvm), or jsverify (js), spec (clojure).

FWIW, rantly (https://github.com/abargnesi/rantly) rather than propr seems to be the most active and usable Ruby property based testing library.

(I maintain a list here: http://hypothesis.works/articles/quickcheck-in-every-languag...)

Ah. Thank you for the links. Also, thanks for hypothesis.
> My last job was in C#, and it seemed like so many things were stringly typed, entirely nullable, or used something else to weaken the type system.

This sounds like C# as written by people that don't really know or like C#. If you're stuffing everything into strings or a dynamic, you missed the point of the type system in a very serious way.

> In addition to this failure to use the type systems, testing is becoming a much more rich environment. I think popular resources on effective testing are much better and numerous than popular resources on effective typing. There are great books and tools around helping developers test their software more thoroughly and efficiently. Comparing these testing resources with typing resources, the closest popular book I can think of for typing is Design Patterns, and I don't think most popular type systems are built with the program correctness as a top priority.

There is some overlap between problems that can solved by type systems and test suites, but there are many problems that can only be caught by one or the other.

It also strikes me as strange that people can't be bothered to use the type system provided by the language, but they can be bothered to maintain and add unit tests for their programs. Correctness enforced by types is fused with the code; if you get the types right, you should be all set. There's no additional maintenance beyond making sure that the code compiles.

(comment deleted)
> This sounds like C# as written by people that don't really know or like C#. If you're stuffing everything into strings or a dynamic, you missed the point of the type system in a very serious way.

I agree, but I think it is the reality. Software development is a job, not divine appointment. And assuming it is reality, I think the best short term gain those developers isn't doubling down on type systems.

> It also strikes me as strange that people can't be bothered to use the type system provided by the language, but they can be bothered to maintain and add unit tests for their programs. Correctness enforced by types is fused with the code; if you get the types right, you should be all set. There's no additional maintenance beyond making sure that the code compiles.

I'm totally with you. I remember a friend being so impressed when a hypothesis test caught a bug in an example that I set up. The funny thing was that I made the example from a function over an F# Union. A lot of people seem to get super irritated by compiler errors but seem to be really impressed by clever testing. I think it has to do with the error messages. Property based testing frameworks tend to show you the simplest concrete example of your error. Type checkers tend to show you the most abstract example of your error. Maybe if type checkers started to to incorporate more concrete examples of error, people would generally find them more appealing.

> Property based testing frameworks tend to show you the simplest concrete example of your error. Type checkers tend to show you the most abstract example of your error. Maybe if type checkers started to to incorporate more concrete examples of error, people would generally find them more appealing.

I think this is a really important insight! And in fact, there is some recent research about making type checkers produce counterexamples: http://lambda-the-ultimate.org/node/5355. Ergonomics of type systems is an unfortunately understudied area.

There is a real cost to properly modeling your domain in the type system. You have to conform with the constraints of the type system, there is higher cognitive overhead, it can cost more up-front (which can cause missed deadlines), etc.

Also, there are cases where using strings is more appropriate than using enums. Where passing a blob is preferred over guaranteeing the correctness of the blob's structure. Where "null" really is better than an Option type.

And conversely, there are companies who hire crappy developers or don't give their good developers enough time to learn the idioms and best practices of their tools.

> Where "null" really is better than an Option type.

WAT? Give me an example.

performance
There's no reason it shouldn't be a typed null though. It doesn't have to be a tagged Option type.
I'm talking about practical reasons to use null.

Theoretically it's not necessary, but given real-life constraints, there are most definitely times when null is more appropriate than e.g. Java's Optional.

That's an extremely weak argument. Java's Optional is broken and should never be used.
If that is true, then it's another example supporting my point.
No. Java messing things up as usual, doesn't have any broader impact on the usefulness of the concept.

Error handling with null is wrong and broken, regardless of whether the null is "typed" or not.

null might have some very limited other uses, but they will never compete with those structures designed for error handling, because they do something fundamentally different.

My argument is practical, not theoretical. Platitudes and ideals are disposable when you have to actually build something.
Yawn.

Java is the platform with NPEs and null checks everywhere. If that's your definition of practical, sure, go ahead.

Could you expand on this?
Optional#map is implemented incorrectly.

   someOptional.map(a).map(b) NOT EQUAL TO someOptional.map(a.andThen(b)
Ah, that's unfortunate.

Because of the order in which side effects occur, or is there something else as well? (I haven't Javaed in anger since before Optional).

Consider two functions (excuse my Scala):

  scala> val str: Function[String, String] = s => if (s.length > 3) s else null
  str: java.util.function.Function[String,String] = $$Lambda$1369/859456754@2c668c2a

  scala> val num: Function[String, Integer] = s => if (s == null) -1 else s.length
  num: java.util.function.Function[String,Integer] = $$Lambda$1376/1887041776@121cf6f4
With Optional, you receive different results depending on whether you call map twice, or combine the functions first:

  scala> Optional.of("Foo").map[String](str).map[Integer](num)
  res12: java.util.Optional[Integer] = Optional.empty

  scala> Optional.of("Foo").map[Integer](str.andThen(num))
  res15: java.util.Optional[Integer] = Optional[-1]
This is incorrect. (They decided to disallow null as Optional's value for whatever insane reason.)

It also leads to all sort of weird usability issues: The old "`someMap.get(...)` returned null ... now does that mean the entry doesn't exist, or the entry exists and it is null?"

In fact they are currently working on special hacks to Java 10's Generics, because they realized that important collection classes don't work well with specialization, and even their new Optional can't save them. (Not directly related, but a good example how design mistakes combine and cause even more pain down the road.)

I agree! I'm just saying there's no theoretical reason for null not to be typed (which is quite different from Optional).
Then we agree :). From a correctness perspective, nulls are awful.

My point was not specifically aimed at typed nulls and compiler optimization, it was that there will always be practical constraints that force us to compromise static guarantees in order to get things done in a reasonable amount of time. As software engineers, our problems are first practical before they are theoretical.

"Dynamic Witnesses for Static Type Errors* ("or ill-typed programs usually go wrong)"[0] by Eric Seidel et. al. discusses a prototype for a system that searches for concrete examples of ill-typed inputs that break a program, in order to aid novice programmers with type errors at compile time.

[0]http://eric.seidel.io/pub/nanomaly-icfp16.pdf

That is really cool! When will it be in GHC? :D
> there is higher cognitive overhead

In my experience, the opposite is the case. If I can offload some checks to my tooling, I don't have to be constantly performing them mentally. And with type inference at a repl, I can ask about the shape of things that can't possibly run yet (though I don't think I get that with C#).

>My last job was in C#, and it seemed like so many things were stringly typed, entirely nullable, or used something else to weaken the type system.

I see this happen when a bad database design was born before the C# code, e.g. varchar NULL types everywhere.

That does happen, but I think the issue is more with education around using types. A lot of people nowadays spend a few years in python/js/java/etc before they have the chance to spend time in a community that really embraces types as the fundamental design tool. Also, it is hard to see the benefits of good typing if my first statically typed language is Java and I'm constantly told to cast into and out of Object. So, I put a lot of blame on the way people are taught to program. People tend to have a pretty hard time uprooting years of experience.
Could you offer a learning path for one with experience in js/python?
I don't know if i could offer a "path", but here are a few ideas to try out:

1. Start using hypothesis or jsverify. 2. Play around with Elm. 3. Participate in a community that highly values types.

## Properties vs Types

Just to explain a bit, types and properties are about program correctness. So they serve the same purpose for us. Types are essentially properties that your type checker can prove for you. Sometimes it is hard or impossible to prove something, so you might reach for property based tests to "approximate" the proof.

So when I'm thinking about my code correctness: 1. write a type where I can 2. write a property where I want to write a type 3. write examples to keep me grounded

Property based testing frameworks will help you move up that ladder.

## Elm

Of course, it will also be helpful to start actually leveraging a type system. I think Elm is a good option for people who are new to using types this way. It has a powerful, but relatively simple, type system that can help you learn good type design without having to deal with all of the features that Haskell/Scala/Rust/Ocaml/etc would have. Also, it has pretty great compiler error messages. So it should be less intimidating than a lot of nearby options.

## Community

Community might be the most important element. Participating in a community that values types (again, I suggest Elm) will be a great investment. Downloading Elm and a property based testing library could be a recipe to feel overwhelmed. Be sure to have a slack channel/mailing list/irc channel ready to help you when you start to feel overwhelmed.

I spent 10 hours yesterday refactoring and adding features to a client side enterprise application that is well over 20k lines of TypeScript over tons of files.

Yesterday was a pretty productive day for me. TypeScript easily caught hundreds of little errors as I coded and refactored which I fixed just a quickly. Most of the time when I tested my changes things just worked. I thought to myself every time TypeScript found something, 'wow I just avoiding another runtime error, thanks'

Maybe people don't write or maintain client side software at scale. I don't know. But there's so little cost to having static typing I don't know why you wouldn't want it. I am easily an order of magnitude more productive with it. I code faster, my code works first time around more often, other people can understand my code easier, and my tests don't have to worry about validating types so they can be more logic oriented.

There is zero chance anyone would of been able to be as productive as I was yesterday if that code base was dynamically typed. Not even close.

This is something I hear time and time again once a person has actually learned the technology (in this case, TypeScript) and seen the benefits in practice.

For many pure front-end devs that have never really worked with static typing, the compiler just seems like another arbitrary build step in the pipeline with no discernible benefit.

"…a client side enterprise application that is well over 20k lines of TypeScript over tons of files […] But there's so little cost to having static typing I don't know why you wouldn't want it"

I don't think the OP would claim TypeScript is a bad idea, as, with its relatively weak type system, it doesn't really suffer from "if you find a bug it’s easy and unintrusive to add a test for it, but may require a substantial amount of work to refactor your code to add types that make the bug impossible".

As an example of a strong type system, in Haskell code, the database access layer will may types such as "ISO-8859-1 string of at most ten characters"

If it turns out that you want to store ISO-8859-2, changing that type can and often will cause a cascade of necessary changes, for example because you're calling a ISO-8859-1 to UTf-8 conversion function, and the compiler will not accept that call to now take a ISO-8859-2 string, or because you are concatenating that string with another ISO-8859-1 one. Then, you will have to decide whether to convert locally or whether to also change the type of that second string, etc.

Now, of course, each of those compilation errors becomes a bug if you just use variable-sized byte arrays. Question is how much work you are willing to do to prevent those bugs.

Exactly. As with everything, there is a tradeoff, and the general answer is always "it depends". Or if you have to choose between one or the other, the answer should be "yes".

Static typing provides universal guarantees, which sounds great at first, until you realize that it really does mean "universal". Sometimes that extreme rigidity makes changes very difficult, especially when you're dealing with cross-cutting changes across many teams and organizations.

I work in a very large company with lots of services. The best practice, espoused by most of the senior/principal engineers (particularly ones with extensive SOA experience), is to keep your service interfaces and document models dynamically-typed. It makes them much easier to change. Big systems should be assembled from loosely-coupled components which are internally rigid but externally flexible and dynamic.

Service interfaces should be well-typed. Almost by definition, the cost to update a service interface will be a fraction of the time associated with updating its consumers and testing their integration. Dynamic types can lower a part of the cost (although not by much, in comparison to modern languages and frameworks), but add more expense in many other ways.

When operating at any sort of scale, a system that's "easy to change" stops sounding virtuous. Communicating and integrating service changes effectively is usually more expensive than making the changes themselves, for a large organization. Providing service descriptions which can be converted into static types (e.g. swagger) is very cheap, and can prevent a host of expensive human errors.

In truth, poorly specified services or documents can end up impossible to change, since the risk and impact of doing so is difficult to size (or the exercise of doing so costs more than the change is worth).

A system that's easy to change is very important at scale, unless you want to write a new API or service every time a new business requirement arises.

In many situations, a true statically-typed service interface can be nearly impossible to change, or even add fields to. Adding new schema versions (particularly for write APIs) or dynamic/polymorphic schemas that change based on the input are extraordinarily useful and very easy with dynamically-typed interfaces, but can be very difficult with strict statically-typed interfaces, particularly the kind that e.g. Java nudges developers towards.

There are some hybrid frameworks that mix dynamic and static typing, which solve some of these problems (e.g. protobuf, Avro, Ion). They make reasonable compromises.

> I don't think the OP would claim TypeScript is a bad idea

Confirmed.

TBH there are very few languages that I'd claim were a bad idea. The point I'm making is not "Statically typed languages are a bad idea" it's "If you think statically typed languages are going to make software correctness magically better then you're going to be disappointed".

You did actually claim that statically typed languages were worse than dynamically typed ones for correctness:

> My general experience of the correctness software written in fancy statically typed languages is not overwhelmingly positive compared to that of software written in dynamic languages. If anything it trends slightly negative.

I might not say that the use of any language is a bad idea, but I'm definitely going to say that basing a language choice on a general and uncertain impression derived from my extremely limited, biased and anecdotal sampling as an end user is a terrible idea :-p

ETA: The point of that anecdotal impression was not "This is definitely true" but "Given that this is my general experience it would be surprising if the actual trade offs experienced in practice were such that static typing was regularly a clear winner", which I feel much happier supporting on an impression than "You should do this".

Well, I would say that some types like "eight-bit character in ISO-8859-1 encoding" is a monumentally stupid idea in this day and age (and yes, I live in 8859-2 area, but that isn't the point … http://www.joelonsoftware.com/articles/Unicode.html).
I would have written PETSCII, EBCDIC or shift-JIS instead, but feared few users would understand it.

Not everybody writes new stuff; some software has to interface with stuff written decades ago.

Also, even for software 100% written today it often is impossible to stay in a world where all strings use compatible encodings.

Examples where you need conversions: when connecting systems that support different Unicode versions or when generating http headers, windows file names, or QR codes. I guess many 'older' systems will not handle ZIP or tar files that use UTF-8 for file names in the archive, either.

With a type of String @@ ISO-8859-2 everyone knows what a function expects. And if you depend somewhere on a String being ISO-8859-2 but everyone hands you in Strings of many encodings, you have hard to debug errors (b/c all main stream languages do not show you where your vars have been created or changed in the flow).

I have a smaller Scala code base to maintain (around 50k lines) and it is a breeze b/c many types are annotated, e.g. Money @@ Netto or String @@ InvoiceNumber. Beside Option this has been the greatest productivity gain vs. Java.

>There is zero chance anyone would of been able to be as productive as I was yesterday if that code base was dynamically typed. Not even close.

My team did a big refactor on our ClojureScript code base last week. We've been developing the project for over a year now. We had no trouble with type refactoring. We use Schema (https://github.com/plumatic/schema) to validate the data at the edges, and the REPL to run code as we write it. Type errors simply have never been a problem for us.

Are these cases easy in ClojureScript -

I change the property 'Part' to 'PartList' on my DTO and now I need to find all places that Part is referenced and update them.

I have a function that takes a 'User' as a parameter, but I don't know what the property is called for the display name. Is there an easy way of figuring it out without running the app? REPL wouldn't help because I dont even know how to create a User object (this is a very large code base that I didn't write myself)

I add a new parameter to a render() function and I need to find out all the places the function is called so I make sure to update them.

I make a spelling mistake calling a function or referencing a property. Do I get any instant alert to the error or do I have to run the app, or run REPL on it?

If I'm changing tons of code across many files I don't want to have to run/REPL for every little thing to validate that I didn't make a dumb mistake. Ideally the mistake is caught as soon as I type it. Does ClojureScript have this?

>I change the property 'Part' to 'PartList' on my DTO and now I need to find all places that Part is referenced and update them.

Yup, I use Cursive IDE (https://cursive-ide.com/) and it provides precise symbol lookup using static analysis.

>I have a function that takes a 'User' as a parameter, but I don't know what the property is called for the display name.

Clojure is a functional language so I don't have a problem of dealing with a plethora of classes. The data model is defined using the schema and I can easily tell what it looks like. Furthermore, since it's a functional language, I don't have any global state to worry about. I can try any function in the REPL in isolation to see what it's doing and how it behaves with different inputs.

Destructuring (http://clojure.org/guides/destructuring) is alos commonly used for readability in Clojure. When a function takes a non-primitive type, it's idiomatic to destructure it to show what the shape of the data is right in the arguments.

>I add a new parameter to a render() function and I need to find out all the places the function is called so I make sure to update them.

Yup, absolutely. I can look up usages of a function in Cursive.

>I make a spelling mistake calling a function or referencing a property. Do I get any instant alert to the error or do I have to run the app, or run REPL on it?

Yes, Cursive will highlight undefined symbols as well is invalid function arity. I get immediate feedback if I mistype something, try to use a function that doesn't exist, or pass wrong number of arguments to a function.

>Ideally the mistake is caught as soon as I type it. Does ClojureScript have this?

That's generally exactly what happens in my experience, and in addition I also have the REPL that lets me run any code if I'm not sure about what it's doing.

Interesting, thanks. It seems pretty 'statically' typed to me. Kind of like TypeScript on the surface, but underneath the tooling and transpilers it is not. What do you think?
ClojureScript is dynamic, but it has completely different semantics from JavaScript. It just treats Js as a compile target.

I think that Clojure has much cleaner language semantics than JavaScript, and this makes the code much easier for the IDE to analyze it.

That said, there is optional static typing available with Typed Clojure, and it can even generate type annotations from tests as seen here: https://github.com/typedclojure/auto-annotation

All these cases are also easy to trivial in Smalltalk.
Refactoring's a different ballgame, where the cost of static typing has already been paid and the benefit is larger (primarily because the risk of type errors otherwise is much higher than with new code). Since most code spends far more of its lifecycle being modified than being written in the first place, that makes the case for static typing much stronger, but I still share the OP's skepticism about static-typing advocates' claims.
Well, the real cool thing about typescript or python's type hints is their optional nature. It means you can get the best of both worlds: the quick script and the big project, the pro and the beginner, etc.
Both proponents and detractors of static typing seem to miss its major benefit: tooling.

Being able to hit . and get a reliable list of things I can do is worth many millions of dollars to me.

> Being able to hit . and get a reliable list of things I can do is worth many millions of dollars to me.

...and mostly is possible because of the many millions of dollars that were thrown into developing it rather than the type system. Quality of tooling has much more to do with the amount of money invested in it than whether the language is statically typed.

But this isn't a post about static vs dynamic typing as I said right at the top. It's a post about the effect of static typing on software quality.

Code completion is a fundamentally harder (if not impossible) problem with dynamically typed languages, as you might need whole-program and control flow analysis to even get the types of local variables. This fact has nothing to do with the money involved, it's probably rather another formulation of the halting problem.
> Code completion is a fundamentally harder (if not impossible) problem with dynamically typed languages

Code completion tools originally came out of Smalltalk. It's relatively doable if you can attach to a running process that has dynamic access to your code.

So that's "not necessary". It's also not sufficient: Most statically typed languages simply don't have this sort of tooling because it's very hard to write, and advanced type systems make it even harder.

Most Smalltalks didn't really have great code completion (or code completion at all), and when they did, a lot of it had to do with the verbosity of method signatures removing a lot of ambiguity. Don't get me wrong, Smalltalk has some really cool features, but they did it by having a workflow completely different from static languages, and trying to imitate Smalltalk's environment in a language not designed from the ground up for it (ie, python or perl or whatnot) would be a lot harder. The fact that it's hard to write language tools in general doesn't change the fact that it's way harder to write an IDE for a dynamic language than it is for a static one.
> Most Smalltalks didn't really have great code completion (or code completion at all),

Ditto most statically typed languages.

> The fact that it's hard to write language tools in general doesn't change the fact that it's way harder to write an IDE for a dynamic language than it is for a static one.

It doesn't, but the fact that there's nearly as much variation within statically typed languages in terms of how difficult this is to do as there is between statically and dynamically typed languages does. And this only gets worse as we move to the sort of languages that the static-types-for-correctness people advocate for.

> It doesn't, but the fact that there's nearly as much variation within statically typed languages in terms of how difficult this is to do as there is between statically and dynamically typed languages does.

I really don't buy this. Yes different languages present different degrees of difficulty, but the ceiling is much lower for dynamic languages compared to static ones. In dynamic languages, there are absolutely (very common) situations where types cannot be accurately inferred (or inferred at all). If I write

   def f(x, a): return x[a]

That's practically impossible for an IDE to do anything with.

The reason why not every static language has great tool support is more to do with A) if the compiler was designed with hooks/apis for tools to use (like clang and rust do), and B) if the language has enough popularity for anyone to bother. I'm pretty sure if you take two languages of relatively equal popularity, and compare the dynamic one to the static one, the static one almost always has better IDE/tool support. For instance, lets take Swift vs Ruby (they're next to each other on the Tiobe index). I've yet to see a Ruby IDE that is much of an improvement on a text editor, whereas Swift is pretty young and already has tooling comparable to much more mature languages.

That function looks like this in Rust: https://doc.rust-lang.org/std/ops/trait.Index.html (it's heavily generic, it could be slightly less verbose if it didn't have to handle Sized)

The compiler will errr if x isn't indexable, or if it is but not by the type of a, in terms of your function's names.

You're right. I autocomplete Ruby with pabbrev in emacs. Basically it remembers what I wrote and suggests me the strings that match the prefix I'm typing. It's great and more that enough. It can't know the number of arguments I have to pass but it rarely matters. All I usually code in Ruby are Rails apps and they are pretty simple, even the ones with dozens of routes and models. Furthermore the location of classes and files is very predictable. There is no need of an IDE to navigate through the code. I came to think that if a language or a framework needs an IDE there is something wrong with it that the IDE must fix.
The reason Swift has tooling is that it has a $600 billion company paying for that tooling. Said tooling plugs into Xcode/clang, which is an IDE for, primarily, Objective-C, which happens to be a dynamically typed programming language (with optional static typing).

For my own Objective-Smalltalk, I added code-completion to both the REPL and a few IDEs fairly trivially.

Jetbrains has a Swift IDE, and they're not a $600 billion company.
> I've yet to see a Ruby IDE that is much of an improvement on a text editor, whereas Swift is pretty young and already has tooling comparable to much more mature languages.

I've never worked with Ruby, but I do work with (and mostly enjoy) Swift. The one thing I really don't like about Swift is that the currently available tooling (both AppCode and Xcode) is extremely primitive compared to what I am used to from Common Lisp and Clojure.

Anecdotal data point of one guy's opinion, with no elaboration on reasons? Convincing. I'm talking about WHY analyzing dynamic languages is fundamentally undecidable in many cases without run time knowledge. Trying to do anything other than heuristics on some of these things is about as decidable as the halting problem. The only argument I've heard against this is you can have a hybrid environment with a REPL so your dead code is live. Ok, but you can do the same thing with static typing too.
>Code completion is a fundamentally harder (if not impossible) problem with dynamically typed languages, as you might need whole-program and control flow analysis to even get the types of local variables.

This pre-supposes that you don't have a test to run the program, because if you do you can just embed an interpreter in it and you have access to everything - tab completion included.

IMO This approach is partly what made IPython notebooks so spectacularly successful, and the future of dynamic language programming will look something like that.

I really think this is what drives nearly all enthusiastic proponents of static typing.

Haskell people excepted.

And the source of most of that tooling: Smalltalk, a dynamic language.

Refactoring Browser: Smalltalk.

Eclipse: used to be VisualAge for Java (written in Smalltalk), split off from VisualAge for Smalltalk.

In my REPL/IDEs with Objective-Smalltalk, I most certainly have code completion, which wasn't hard to implement at all.

You should get out more ;-)

> the idea that static typing will solve all our problems, or even one specific problem: The ubiquity of broken software

Citation needed.

Most proponents of static typing, myself includes, have much more modest claims: that is saves time spent by developers on smaller, more mundane errors. That it makes refactoring easier. That it allows for better IDE support like autocompletion and hover-over documentation.

Even if these advantages are modest compared to fixing "broken software", they're still huge and worth the increasingly minor annoyances of modern static type systems, many times over.

A digression on Java compiler performance:

> Java's compile times are sorta reasonable but are apparently getting worse.

I'm not entirely sure what this is about. Could it have anything to do with JEP 215? [1] There were some slowdowns in `javac` in Java 8, but these should be fixed in Java 9. Note this has more to do with type inference than with Java's type system per se, though this distinction is of little difference to most Java programmers.

In our experience with OpenJDK builds, the main performance issues have been around redundant loading/parsing of library class files and JVM/JIT warmup time. These have mostly been mitigated by JEP 199. [2] With the exception of the issues I mentioned above, it's hard for me to see that static typing has had an impact on the performance of Java compilation.

(Overall I agree with the main thrust of the article, particularly with regard to the "set of complicated trade-offs." Personally I find static typing very helpful, but it won't solve software quality problems.)

[1] http://openjdk.java.net/jeps/215

[2] http://openjdk.java.net/jeps/199

It might be based on false information - I'm a bit out of touch with Java development. I'll remove it.
Thanks for the update. Of course if there are any performance problems, I'm sure the javac team would like to hear about it, as this is something they do pay attention to.
> As static typing becomes more elaborate the scope of the bugs it can prevent becomes wider, but it usually comes with an additional cost: Build times.

This is the meat of the entire argument, and it's false, as you can verify by running ghc with the -fno-code option. Type checking is extremely fast. It's everything else that's slow and that has absolutely nothing to do with static typing.

> Software is broken because its not worth people’s while to write non-broken software

This is the premise of a large part of the article. And it is wrong. At the very least a citation is needed. And even if there is a scientific source, I disagree with it. I'd argue that many people do want to write non-broken software, but they do not know how to efficiently use static type system, or they don't know how effective it is. If all you know is Java or C then of course you're going to doubt if a type system is worth at all.

> As static typing becomes more elaborate the scope of the bugs it can prevent becomes wider, but it usually comes with an additional cost: Build times.

I disagree. When you are working with Haskell, use ghcid and you'll have near instant recompile times. I made a proof-of-concept of a web frontend in Haskell, and it reloads with sub-second delay. That is comparable to a JavaScript project which uses webpack and babel.

You can disagree what you like, but the fact that a lot of software out there has bugs and these bugs don't stop it from being successful clearly supports the point the article makes.

If catching the types of bugs that static typing prevents resulted in reduced costs of software development, you'd see static typing out-compete dynamic typing. It's really that simple.

> If catching the types of bugs that static typing prevents resulted in reduced costs of software development, you'd see static typing out-compete dynamic typing

Apart from network effects, inertia, lack of education and awareness, and all the other confounding factors.

The complete and utter lack of empirical evidence with regards to purported benefits of static typing is the real elephant in the room here I think.

Considering how long both disciplines have been around, you'd think we'd see some clear benefits by now. Yet, here we are swapping anecdotal evidence.

The disciplines may have been around for long. But the industry has poured billions into educating people (students in universities) on C, Java, C++, C#. Comparatively little has been invested into educating people how to use and work with good type systems. You're comparing apples and oranges.

You can't ask the general developer population how they feel about static vs dynamic typing. The result will be biased towards dynamic because that's the only thing most developers know.

Conversely, a lot of people have have used dynamic typing in languages like JavaScript and Ruby. Their OO nature encourages creation of many types, while pervasive mutability removes any possibility to do local reasoning about the code. The situation is completely different in functional dynamic languages backed by immutable data structures.
> even if there is a scientific source, I disagree with it

Well, that pretty much signals how people should interpret the rest of what you say.

There are scientific sources that claim all sorts of wrong things. Not trusting things blindly is called critical thinking.
Science can be wrong, but announcing your disagreement before you've ever seen the evidence is the absolute opposite of critical thinking. It's not skepticism but dogmatism.
Interesting point about the relative value of typing vs. testing, and how too-strict typing can crowd out testing. However, see the discussion under pixie_'s comment about new code vs. refactoring.

Also, this makes me think about incorporating testing directly into the language, which kind of brings us to Design By Contract and formal methods. It seems to me that now would be a good time to resume active research on those things, instead of all these exhortations to use magic type systems and magic immutability and so on.

> I’d expect static typing to win out for correctness if: [...] very large.

I would disagree. In very large projects, strong types don't scale very well across repositories / deployable units.

A very large single repository, it may be ok. Except that refactoring types will like break a lot of in-flight pull requests and branches.

OP's argument, boiled down, is the following:

1) Behavior needs to be automatically checked 2) To check behavior, you can either write tests or specify types 3) Tests are more flexible than the type systems of popular languages (e.g. to write end-to-end tests) 4) Testing purely dynamic languages is quicker than compiling statically typed code 5) So prefer tests to type systems because agility

Honestly this is a load of bollocks and I'll explain why:

* When business execs throw out the old UI (old end-to-end tests) and replace it with a new UI (new end-to-end tests) and your new tests are failing because undefined, well, have fun and good luck! * The longer a system lives, the longer the test suite gets. So no, your developers are not going to be running the entire test suite at their desks, even if at the beginning of that project, running that test suite took all of half of a second. This is one of the reasons for continuous integration servers, actually, so that the massive test suite can be parallelized across a bunch of build slaves and run overnight while the programmer is at home or working on a different feature/problem. And then the difference in compile and start-up times is psychologically irrelevant because the programmer will still think "ok, I submitted the build to CI, time to go home/work on something else". * Good type systems, combined with proper IDE support and proper object-oriented design, do optimize for deletion by reducing the number of places in a codebase in which refactoring has an impact. It is incorrect to state that type refactoring should be some kind of massive refactoring affecting every file in the codebase every time you refactor. A refactor should either eliminate a given subtype, thus leaving the other subtypes unaffected, or add a subtype, where no-longer-common code/behavior can be factored into a new intermediate type. When the supertype needs to be changed, well, you still need to test those changes, but at least you only needed to make the change in one place, and not an indeterminate number of copy/pasted places in dynamically typed code which, you know, good luck finding them all.

Static typing is a practical requirement for long-lived, large systems. OP makes way too many assumptions on the basis of small, short-lived projects.

I watched a talk by Erik Meijer [1] recently where he mentioned that an implementation of a tuple such as (C# syntax mine, but illustrates the point)

  class Tuple<T1, T2>
  {
    public Tuple(T1 t1, T2 t2) { }
    public T1 Item1 => default(T1); // null for reference-types
    public T2 Item2 => default(T2); // null for reference-types
  }
is a perfectly type-safe implementation of a tuple that's completely nonsense. I'm curious as to how you might actually encode into the type system that the values that come out of Item1 and Item2 are actually the values you put in.

I would love to see tests at the interface level (or typeclass level for Haskellers) rather than the implementation level, to enforce that all implementations of an interface satisfy laws like this. In Haskell, this would mean that you could enforce things like monadic laws which we all assume to be true anyway.

[1] https://www.youtube.com/watch?v=JMP6gI5mLHc

If you require a constraint to be able to provide Default values (even if every type in fact implements Default - c.f. Typeable) then for Tuple in particular there is only the correct implementation (modulo bottom or hidden side-effects).
True, that's a good point.

Side note: you seem to be replying to all of my comments, are you following me? :P

> are you following me?

Checking in occasionally - you've been steadily posting things worth replying to :)