I've found that I have a hard time thinking in Microsoft. I can think in C, C++, Smalltalk, Java, Bourne, BASIC, Perl, LISP, and any kind of scripting you could care to name. But, hand me C#, VB, J, or any of the other language Microsoft has designed in the last ten years, and I just can't hack it. Well, I can, but it just seems ... the long way around. Don't get me wrong, this isn't a "Microsft is Teh Evil" thing -- the things they do well, they do quite well, and deserve credit for. I just don't get it when it comes to the .NET platform.
So, C. C is the second general-purpose language I learned, in 1993 (after BASIC, which I learned in 1987). There were various scripting languages I learned in the interim, mainly of the batch-file and variant types, but I would hesitate to call them complete languages like BASIC and C. Oh, I learned Fortran sometime around 1990, but I never did anything much with it so I don't count that either. In any case, by the time I got to college and started actually taking programming classes (in 1996), I had been programming for close to a decade, and knew two languages already. I quickly found new and interesting languages in the form of C++ and unix shell scripting (Korn shell, mostly, although it's close enough to Bourne shell that the scripts were mostly compatible with either shell). I remember taking graph paper to American History class and coding, on paper, things I would type in and try later. I got really good at C, palin old vanilla C, and picked up some x86 assembler books and had a blast. I actually learned Javascript as a language unto itself, as opposed to an augmentation of html, right as the IE 5.0 DOM was being solidified (sometime around 1998).
I got my Associates from that college in 1998, took a year off to work for a bit in the field, and then went to the UW in fall of 1999 for a Bachelors in Computer Science (accepted into the department in spring of 2001, deciding on the more interesting Computer Engineering route instead). I already had been exposed to a number of languages, but it was at the UW that I found opportunities to learn Smalltalk, ML, and LISP. You may have noticed that I haven't mentioned Java ... I learned Java on a whim, because it was enough like C++ that it was a "learn in a weekend" type of thing, at least enough to start hacking in it. Later in my studies, I had to learn a couple different assembly languages for different architectures. I started coding in C# in 2000 (the year it was released). Of course, being a sysadmin for much of that time, I was also introduced to a very large number of configuration languages, interface languages (to dns servers, for example), and scripting languages. I taught myself Perl somewhere in there ... which I also learned over a weekend on a lark, because of its ease of text processing.
At this point, I know a largish number of heavyweight computer languages very well, I can code with a reference for dozens, and I have a smattering of many more that I've bounced across over the years but never had a need for. I've written a LISP interpreter in Java, I've designed and simulated different CPU architectures, and I've programmed things from kernel hardware device drivers in firmware to full-blown user applications with nice graphical interfaces. So, when I'm talking about this language versus that language, the features and the object system, what it's good for and what it's bad for, I'm speaking from 18 years of experience with dozens of languages, or hundreds if you count the lighter-weight languages that form config files and simple script interfaces. I can think in a variety of forms of Object Orientedness, with or without static typing, in functional languages, in message passing, in procedural languages, and in a variety of hardware architectures. I'm no
evan, who I think plays with every general-purpose computer language ever invented (or close), and I don't create my own languages for fun like my friend Water (who created the
Slate Programming Language), but I've used a large number of differing styles and philosophies of programming, and I'm not a complete dolt when I start talking about the pros and cons of different approaches taken by different languages.
Simple languages become more expressive over time. Over-designed languages become less expressive over time.
In short, they look good at first and then get worse. There's been a trend toward more and more overly-designed languages these days, for some reason. Probably because no one bothers to think about how things change over time.
Take LISP for an example. LISP is easily the simplest general-purpose language I have used, whose code consists entirely of space-separated lists enclosed in parentheses (with lists inside lists, once again using parentheses). In LISP, write code that does what you want to do conceptually, how you want to write it conceptually, then implement it underneath to make that simple conceptualization a reality. If you want to be able to say "create a new testing framework subsystem called S under P that runs tests X, Y, and Z that handles errors by stopping", then write "(create-new-test-susbystem S :under P :tests (X Y Z) :onerror :stop)" and implement that function. There is no boilerplate, you just write what you want it to do then figure out how to do it from there.
LISP started a long, long time ago in computing terms, having been created in 1958, the same year as Algol and a year before COBOL. (The first general-purpose computer language is usually considered to be FORTRAN, the precursor to Algol and a good many other languages, developed in 1954. Check
this chart out if such things interest you.) It's been around for ages. It's developed over time, to do what it does easily, and to do it well. It has incorporated the good ideas that come across the ages, such as classes, mutiple dispatch (if all you know is C++/Java/Perl, you have no idea what this is), lambda calculus (functions that generate functions), polymorphism (treating different types of objects in different ways, depending on their type at runtime, like C++ and Java), a package system (to avoid name collisions from code from differing sources), exceptions (to signal error conditions elegantly), and an INCREDIBLE macro system (code that writes code for you, vastly superior to C's #DEFINE or anything Java has). It didn't have any of this at first; it was just a functional language, a mathematician's plaything, designed with "fraction" as a native type. (Can we say no rounding errors? I thought we could. Definitely designed by a mathematician, for mathy purposes.) Pretty much anything you want to do in LISP, you can write it simply, legibly, and easily -- because that's what happens when you have a simple language become refined over time: its expressive power is built, layer after layer. Still, it's all done with space-delimited lists in parentheses. All of it.
Now, on the flip side, look at C#, one of Microsoft's latest languages. C# was released in 2000, and it's essentially a combination of Java, Javascript, Visual Basic, and Delphi (which is essentially PASCAL with classes). In true Microsoft fashion, it is attempting to be everything to everyone, all at once. It has the standard C-style syntax with the {} code blocks and the name(arguments) function calls, but it also has "get" and "set" variables which are just function calls in disguise. It has the Java "interfaces" idea to get rid of multiple inheritance, it has the C++ operator overloading so you can make code that "adds" two strings together using a plus sign, and it has the Javascript-style DOM system built in for web programming. Durned if I know what it gets from Delphi, as I don't know Delphi. Problem is, there's so much junk that in order to write a program, even a program framework, it's a lot of work. Then, to change anything, it's a huge mess going around and tracking down all those places that reference what you're changing. If you don't program much, trust me on this one: You change your mind about how you're going to implement something, and you change your mind a lot. In short, there's so much syntax trying to make it easier to say what you want, and harder to accidentally shoot yourself in the foot, that you can't effectively create anything!
Take a step back for a moment, and lets compare on a different level. C, at its core, is really just prettified assembly. It's really easy to translate from C to assembly: that's why it was designed the way it was designed. The syntax of C was intended to make assembly concepts seem slightly more natural to the human mind, in logical chunks surrounded by '{' and '}', that you could call from anywhere in the program and return to where you called from. It's great for writing fast code, because it's so close to the underlying hardware. LISP, on the other hand, was intended to be much more how a human thinks, to allow a human to say what it wanted to do and then have the computer do the hard work of translating. As such, LISP was a very cludgy and slow language at first, slowly becoming more efficient over time, because of that translation process. C++ is just a small add-on to C, that adds the concept of nested data structure types and having functions as part of a data structure, but it's still very close to C and you actually have to specify when you want the new and improved, slower, runtime-dynamic function calls (this would be virtual functions). You also have the ability to redefine what the standard operators like '+', '*', and '[]' do on specific datatypes. Java, in an effort to "look" like C++ but actually have a nice object oriented abstraction (instead of the hackish add-on that is C++), did away with the faster function calls altogether. Now we have a language that is structured like an assembly program, but is actually quite far away. C# takes this to an absurd level, keeping the old C structure around, and cludging it even further so it acts like an event-driven, message-passing system very much like Smalltalk and very much unlike C. As you might imagine, this means you have to "fake" a lot of structure that isn't really there, which means even for something as simple as changing the value of a variable when a button is clicked, you have to write absurd amounts of boilerplate code -- code that needs to be there but doesn't have any content, like the whirlygigs on a pretty sign ... but without the marketing value.
This is an obvious problem. (Maybe the reason isn't obvious, but the problem certainly is). It looks like Microsoft has gone the "autogenerate the boilerplate" route of resolving that problem. When you create a button in their nifty IDE, it creates a function definition for you atuomatically. A good deal of the boilerplate is already written, making it "faster and more efficient coding" for the programmer. That's great. But, it only does it for the common things, the really common things, the things you wouldn't even consider not implementing. (Why would you have a button if it wasn't intended to be clicked?) If you want to handle some other type of event, of which there are thousands to choose from, you need to write all that boilerplate yourself. This is "advanced" programming. Also, when you change your mind and get rid of that button, or call it something else? Yeah, all that autogenerated code is just spamming up your source file, probably making it not compile. Perhaps Microsoft will eventually get the autogeneration down so smooth that it's just "draw in the pretty designer window" and "fill in the boxes", and that would actually be pretty nice, but it would also be very restricting -- what if they didn't have a box where you wanted one? For that matter, what if they wanted to develop in a different environment, with a different editor? The code generation isn't in the language at all: It's in the editor. Use any other editor and you have to do it all by hand, anyway.
LISP, on the other hand, solved the "too much boilerplate" problem using an entirely different philosophy: Get rid of it altogether. If you find yourself writing many functions that take the same arguments and have similar names -- for instance, to handle button clicks on a webapp -- then you just write a single macro called something like "button-click" with a button argument and a form argument ("form" is LISP-ese for "chunk of code") that creates the desired function. And when I say "create", I mean at compile time, and not spamming your source code. Oh, you want to slightly change every single button click routine, perhaps to log each click? Modify the way that function is generated by the macro, recompile, and BAM! you're done. No need to track down every single "automatically generated" click function for every button in your app, to add the little "log" line at the top. In LISP, there are macros that automatically generate entire classes, complete with member variables and methods, for those times when you have lots of similar classes.
Both methods, auto-generated code and macros, address the same problem of too much boilerplate. But, if you were aware of both methods, which would you choose? Well, if you were someone who just wanted to fill in the blanks and never look at it again, you'd choose the autogenerated code. If you were someone who wanted to actually make something of quality ... well, you wouldn't choose the autogenerated code, because quality means refinement, and refinement means changes, and lots of boilerplate makes changes hard.
As times and programming technologies change, which language design do you think will have an easier time accepting the new ideas? The language that allows you to abstract away any implementation details, or the one that automatically generates the implementation details for you? How easy do you think it will be to update code, as time flies by and new features are requested?