Hacker News new | ask | show | jobs
by reality_czech 4445 days ago
This is the path that all dynamically typed scripting languages must follow. Over time, change becomes impossible because the lack of typechecking or static analysis tools means that any change might break something in a subtle and hard-to-diagnose way. And so the language grows by accretion. You end up with something like bash or perl, where there are a million ways to do any one thing. Each way was added at a particular phase of the language's life, and it could never be removed after that. And so the language becomes difficult to learn and unattractive to newcomers, so another scripting langauge pops up, and the cycle of life begins again.

Compare this to a language like golang, where you can just run "go fix" on your code to update it to the latest version. And you don't have compatibility hell, because when you distribute your application, it's a standalone binary. Stuff like go is the future. Get off the dynamic language hamster wheel.

3 comments

It is easier. On the other hand, Java doesn't drop any deprecated API's either, so static types may not help as much as you think. Migration still has to happen before dropping something.

It seems to be that automatically upgrading your code, even with "go fix", is staying on the hamster wheel. Using a stable language like Python 2.7 is getting off the hamster wheel, since the "hipster" programmers (including me, sometimes) have moved on.

So in a way, Python 3 helps you if all you care about is stability, since people will make changes to 3 and you can keep using 2.7.

The risk/reward ratio is a lot different in those two cases. If a Java library drops support for some API (and many do, in major versions), I will know at compile time, and fix it. If the same thing happens in Python, you may not know until days have gone past and your program crashes. The type system is like a set of unit tests that get written automatically and have 100% coverage.

There is also the issue of how code gets installed on the system. The Python model is that you have a bunch of py files sprinkled throughout the filesystem. In this case, you have a "baling out the ocean with a teaspoon" issue when making a major change. There has to be a flag day when everything changes at once. In contrast, with Go, I can have apps compiled with Go 1.0, Go 1.1, and Go 1.2 co-existing happily on the same system. They don't share library files. (Yes, I understand abut things like virtualenv, but that doesn't help distributions that want to ship your software.)

A big part of why Sun (and now Oracle) has been so conservative about backwards compatibility in Java proper (as opposed to the libraries and ecosystem) is because JDK upgrades have a similar "baling out the ocean with a teaspoon" property. It's all or nothing... you generally only have one version of Java installed, and it has to play nice with everything. If they had integrated the runtime into the binary like Go did, this would be much less of an issue.

I would go one step further: in an effort to maintain binary compatibility, Java ended up trying to do "type erasure with generics", which in my mind is a horrible wart. C# went through the same changes, and built two different libraries for collections to accommodate it.

What I am saying is that it wasn't much prettier in those cases, and golang might not have had the same type of issues yet, but it just hasn't been around for all that long.

Well, Go isn't Java. They are two different languages.

The Java designers wanted jars to be runnable by any virtual machine, ever. This meant preserving some pretty old and nasty hacks (like erasure for generics) to allow old jars to work with new virtual machines.

With Go, you would just recompile and that would be it. Go doesn't even support loadable modules yet (although it's coming soon), so they can do whatever they want with compat. It's unlikely they'll make the same mistakes Java did because they are not blinded by the "write once, run anywhere" ideology.

As much as I'm drawn to languages such as Go that are designed for ahead-of-time compilation to self-contained executables, I don't buy your claim that dynamic languages are particularly susceptible to degeneration. As others have pointed out, Java and C++ are both statically typed, and they've both accumulated considerable cruft over the decades. Go is cleaner primarily because it's newer.
C++ was a mess on day 1 due to the decision to build a sorta-kinda higher level language on a C foundation that was never meant to be used that way. The fact that it was designed by a committee and animated by a philsophy of "features, features, features" pretty much explains everything you need to know about what's wrong with it. Does it really count as degeneration when you're born with the defect?

Java hasn't really acquired that much "cruft." The cruft that it does have is mostly relics of really poor design decisions that were made (again) on day 1. For example, the decision to support thread cancellation, which the designers later realized was dumb, has left a bunch of defunct APIs in the Thread class which you're now not supposed to use. I already talked about how the need for jar compatibility (due to the misguided portability religion) led to the generics issues. Java is an ugly language in some ways, but you can't really say that it degenerated: it was that way from the beginning. In fact, now is probably the prettiest it's ever been (not saying much).

The larger problem of 'language drift' seems inevitable- C++ from 15 years ago will have auto_ptr's sprinkled throughout, Common Lisp (compared to Scheme) has had stuff added and added to it, Java has more stuff tacked on as it grows... but it only really causes a problem when you have something like a dynamic language because you're using one interpreter (usually) for the whole project, and so anything breaking backwards compatibility causes huge problems. (Add to that that interpreted languages tend to have more changes in syntax more quickly than compiled languages).

Could dynamic languages avoid this by having (say) a byte-code compiled backwards compatibility module? I would imagine it looking something like this for Lua:

luac -version=5.0 module.lua -o module.o

and then call into whatever functions you need from module.o in the rest of the project, using the new interpreter for whatever doesn't need to use legacy features.