Shared publicly  - 
 
Prototype Languages are a Failed Experiment

I remember reading long ago - maybe from Hejlsberg? - that Java's checked exceptions were a "failed experiment". Most people agree, and as much as I still like checked exceptions (and I can back that with tons of code...), it's a fact, in the sense that Java's experiment failed to convince most people - which you can assert by looking at newer languages. Not a single other programming language adopted this trait of Java. Language designers make decisions with various motivations, but you won't find checked exceptions in any kind of post-Java language, no matter if it's created by PhD researchers, big corporations, free software hackers with their different itches, or monkeys with their infinite typewriters.

The failure is bigger: I have fond memories of Eiffel, I didn't use it professionally but played a bit, Meyer's OOSE book was my first Bible (I mean it, as in, adoration-to-sacred-commandments). But the heretics have won and DbC is a mostly dead idea. Heck, even Java 1.4's modest assert is virtually unknown and unused.

Now, what can we tell about prototype-based languages - those where behavior is reused by cloning objects that first define it? A bit like Java for checked exceptions, #Javascript is the big champion of the prototype paradigm. It's the single successful and mainstream language, ever, to espouse the idea. Self and Cecil were research languages; Lua is successful but not mainstream, and its niche (game scripting) doesn't need programming-in-the-large qualities.

More important than that is looking at the "legacy" of Javascript: Languages designed after Javascript, remarkably ones that compete[d] in the same area. I could name VBScript, Curl, ActionScript, F3 / JavaFX Script, Silverlight (several languages although none especially created for that)..., and of course Google's new #Dart. Not a single of these languages is prototype-based. Flash's ActionScript 1 was, but this is the first thing Adobe fixed - v2 introduced class-based OO as a layer over prototypes, and v3 embraced classes for good (prototypes are still available but definitely a second-class, legacy feature).

[Personal note: I've used Pic%, a prototype-based OO language built on top of Pico, that one being a Scheme-like academic language. I was lucky enough to have Pico's creator Theo D'Hondt as professor in my MSc (EMOOSE ftw!), where I spent long nights in assignments to modify Pic%'s metacircular implementation.]

Java's checked exceptions (and everything else from Java) had a chance to mature: production-grade implementations, tooling, community / ecosystem. This is critical to really judge language innovation - if some language is never widely adopted, advocates of its unique qualities may claim they were just not developed to full potential. Javascript has no such excuse today; the enormous investment from all Web industry placed Javascript in a very select club. There is no shortage of runtime technology, tooling (IDEs etc.), libraries. High-performance VMs are a late and still ongoing development, but it's safe to state that all low- and medium-hanging fruit were already harvested, the top VMs are already in the diminished-returns curve. Same goes for tooling and other pieces of the puzzle, everything is already mature i.e. don't expect any major improvements from now. Unless new releases of the language make it easier to optimize, refactor, modularize, etc.; but then, that's not the same language. ES5 (Harmony), the next major release of Javascript, is yet another Javascript derivative to put prototypes in the past. Its support for real class-based OO and optional static typing, like ActionScript v2, give the finger to the language's roots, relenting from pressure from end-users and compiler/tool writers alike.

Wikipedia summarizes the criticism for prototype-based languages, but misses one item that seems important to me: prototypes are highly coupled to dynamic typing. I know of no prototype-based language that's fully and strictly static-typed; I mean not using qualifications like "optional", "lenient", "runtime", "partial", etc. Class-based languages, on the other hand, can be perfect matches for either static or dynamic typesystems (or anything in between). Henry Lieberman's seminal paper on prototype languages shows that delegation is more powerful than inheritance; this has been widely read and quoted as "prototype languages are more powerful than class-based", but that's bollocks, remarkably with the modern crop of OO languages and techniques, that both support delegation pretty well and deemphasize inheritance compared to the first-generation OOPL's. Today, the comparison boils down to the fact that prototype-based languages are much less flexible in the critical aspect of compatibility with different typesystem strategies.

Prototype, n., from greek "primitive form". RIP.
8
3
Leonardo Galvao's profile photoJJ Lemire's profile photoGilad Bracha's profile photoDmitry Cherrymissoff's profile photo
6 comments
 
While I tend to agree, I think it is a bit unfair to conclude too much about prototypes in general based on Javascript. Javascript's prototypes are very different from those in Self. They are limited to a single parent (it's crucial to support at least 2, for compositionality) and suffer from a complex model involving constructor functions.

I also agree it's hard to support static typing - I just don't think that matters all that much :-).
 
+Gilad Bracha Agreed on the unfairness, unfortunately many language features are not well represented by the most popular language to adopt it. I wonder if Java's checked exceptions would have another fate if better designed since day one (with try-with-resources / multi-catch / final rethrow, no abuse/misuse in core APIs, etc.).

On static typing, this used to be a very black-and-white debate in the past, but today it's very interesting to see a much richer panorama of ideas and options. It seems both "teams" pushed their paradigm as far as reasonable and then some - Ruby and Javascript; Haskell and Scala... - and now we are starting a new evolutionary phase, to trim out baroque excess, pick the ideas with proven good benefit/cost, and even combine paradigms when it makes sense.

When I think about this, Dart has another feature that has been missing in the last batches of languages: a distinction between debugging and production builds/execution (because the validation of static types is an optional, development-only feature). I don't do that since I moved from C/C++ to Java, and at that time everybody was so happy to get rid of the preprocessor; the compiler output and the runtime behavior are one and the same from development to production, from one platform to another. But completely losing macros was probably throwing the baby out with the baby water - once again, the C preprocessor was a weak implementation of that, but it's what everybody knew (and hated). Dart's checked mode (IMHO) is not the same thing as javac's warnings or validation tools like FindBugs... I'd rather compare it to something like JSR-305, where the programmer also contributes information (intention) to an optional, extensible, tooling-centric typesystem.
 
this is a bit brutal but I think exceptions altogether should be removed.
This is coming from LISP. The other way is to use the Go system:
a function/method returns many values: you check of the value as an exception if you want.
 
+JJ Lemire Yes it is a brutal idea, unless you have good motivation to not want exceptions at all? On static. vs dynamic typing, I favor static but I know that dynamic typing have some important advantages too, in the end it's a balance and each person have different weights and also preferred programming techniques that favor some kind of language.

But error handling without exceptions is not similar; it'd be hard to advocate for C-style error handling (...or should I say Assembly-style; it's a non-feature). Go's tuple returns are not a new idea, and tuples were never considered an alternative to exceptions. Go's main innovation is that it is a systems language, it's roughly in the C level of abstraction and efficiency; in that space tuples are a cool new feature and yeah, a nice convenience for C-style error handling. But tuples only solve a single problem of C: the problem of domain, e.g. if the main return is an int but there are 7 possible errors, you don't have to reserve restrict the main return's domain to 2^32-7 possible values. But this is far from being the only problem of C-style error handling. It's still way too easy to swallow errors without either checking or propagation. A compiler could issue warnings for that, but let's be serious this is not as good as automatic propagation of all unhandled errors. In Java you can write an empty catch block, but that's a explicit code to swallow the error: doing this implicitly is The Wrong Thing (TM).

Returning multiple unrelated errors is another problem. Suppose my openFile() wants to return a "invalid file name" if I pass name==null, or "file not found" if the name is valid but there's no such file. Returning a single error code, or even a single error type with value selectors like BAD_ARGUMENT and NOT_FOUND, is Gross. You can of course return a 3-tuple with two possible errors, but that's even more insane if you consider the consequences.

In a subtle variation of the previous issue, there's the problem of composition: f() invokes both g() and h(), but each of these called functions return unrelated error types A and B, and f() doesn't want to handle any of them locally? You can try the alternative of unification: f() handles both A and B, but maps them to a single error type C that flags the failure of f() instead of exposing A,B to its caller. This looks good at first sight (encapsulation! loose coupling!!) but the idea has major flaws. You'll write extra boilerplate to handle/remap errors, and more distinct error types polluting the design/API. But the fatal flaw is flexibility, because the encapsulation of exceptions produced by a function's implementation is not always desirable. Actually it's the opposite: a good idea for some cases (like Facades and SOA-like service interfaces / coarse-grained APIs) but in the vast majority of the cases, the pass-through of most errors is the best design. We can debate this, but the fact is, exceptions give me the design choice of encapsulating or exposing errors, while return values only allow me to encapsulate them (unless I accept other disadvantages, as exposed).

In summary, Go's tuples are nice, but not a replacemenet for exception by a long shot. This is old stuff, a pretty mature part of language design and the jury has long decided in favor of exceptions. Even the system-level / high-performance nature of Go does not warrant exclusion of exception, because C++ has the feature and its performance cost is basically negligible with modern compilers (see http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf, 5.4.1.2). Go gets it even easier because it doesn't have manual memory allocation or C++ destructors, so stack unwinding would be much easier, requiring the generation of much simpler code and metadata (just like in Java).
 
Osvaldo, voltando a escrever! :) Que tal fazer uma versão desse teu texto para o InfoQ Brasil?
Translate
Add a comment...