A Wish List for the Next Mainstream Programming Language 16

Posted by Michael Feathers Tue, 30 Dec 2008 02:06:00 GMT

It’s been fun watching the reactions to new features in C# 4.0. Some people love them. Others wonder, legitimately, where it is all going to end. The argument for feature addition is simple: over time we find better ways of doing things and our languages, as tools, should allow us that flexibility. The only downside to this strategy is that you end up with sprawling, complex languages over time – you never get to revisit the foundations.

Fortunately, however, people design new languages all the time and some of them do eventually enter the mainstream. We get a chance to start over and address foundational problems. And, that’s nice because we can do better than Java and C# for mainstream development and I don’t think there is any way to mutate either language into a better foundation.

Before I launch into the wish list, however, I want to set the context.

When I say “mainstream language” I am talking about languages which are in the Java/C#/VB market space – languages which are light on rocket science, seen as suitable for large-scale development, and don’t scare people. So, I’m not going to suggest dynamic typing or (on the other side of the coin) tight Hindley-Milner type systems and lazy evaluation. I love those approaches and I’m happy (in particular) that Ruby is gaining widespread acceptance, but I’m not going to fight that fight here. In the immediate future, for whatever reason, there will be development shops which feel much more comfortable with traditional static typing – the kind found in Java, C#, and VB. Given that, the question becomes: what can we do to make that sort of language better?

Here’s my list:

  1. Immutability by Default – Over the past few years, a rather broad consensus has emerged around the idea that code is easier to understand and maintain when it has less mutable state. This isn’t a new idea; it’s been around for as long as functional programming, but our recent concerns with concurrency and our move toward multi-core computing just underscore the state problem. A mainstream language should, at the very least, make mutable data something special. References should be immutable by default and mutable state should be marked by a special keyword so that its use leaps out at you. It’s too late for the current crop of languages to make such a pervasive change, but the next mainstream language could.
  2. Support for Components – In large-scale development, teams have to manage usage and dependency across an organization. The notions of public, protected, and private are too coarse as protection mechanisms, and we really need scopes larger than class, assembly, and package. In the end, this sort of protection is a social issue, so we should have mechanisms which make use very easy within a team (3-10 people working together) and somewhat more manageable between teams. It’s odd that language-based support for this work stopped with Ada and the Modula family of languages. Java’s recent move toward support for modules seems to be an exception.
  3. Support for Scoped and Explicit Metaprogramming – In the past, language designers avoided adding meta-programming support to their languages because they were scared it would be abused. However, we’ve learned that without meta-programming support, people create yet another distinctive type of mess. If there is a middle ground for mainstream languages it probably involves scoping the use of metaprogramming and making it far more detectable. If, for instance, all of the code which modifies a given component had to be registered with some easily locatable component-specific construct, maintenance would be much easier.
  4. Support for Testing – This one is only a matter of time, I think. In the last 10 years we’ve seen an explosion of mocking tools and testing frameworks. It’s not clear to me that we’ve reached any sort of consensus yet, but I suspect that at the very least we could add constructs to a language which make mocking and stubbing much easier. It’s also about time that languages attempt to solve the problems that dependency injection addresses.
  5. Imposed I/O Separation – This is the controversial one. The more I work with Haskell, the more I notice that there is a beneficial discipline that comes from factoring your program so that its points of contact with the outside world can not be mixed with the pieces doing the work. When you first start to work that way, it feels like a straitjacket, but I think the benefit is apparent to anyone who has had to go on a spelunking expedition in an application to find and mock parts of the system which touch the outside world. Is that discipline too much for a mainstream language? I hope not.

So, that’s my list. There is no “grand language planning board” which decides these things. We will move forward chaotically like ever other industry, but I do hope that some of these features make it into the next mainstream programming language.

Comments

Leave a response

  1. Avatar
    Olof Bjarnason about 2 hours later:

    Great post!

    Some thoughts:

    1: completely agree. Immutability is something that should be default! I’ve taken this lesson from my Haskell courses at school.

    2: A simple idea: do away with ‘em completely. They’re just causing more harm/hassle than they buy.

    But thinking about it for more than a few seconds—this is a tough question – one that I feel reveal the conflict between testability and interfaces/IntelliSense/code clients.

    One idea would be for classes to implement some kind of hinting towards the compiler, similar to overriding Equals() and GetHashCode() in java/c#. Question is – would it be better than public/internal/protected/private/whatnot?

    A try at expressing the general problem -
    • code has different audiences/masters at different times. One master is the “package close audience” – those classes using the objects internally to solve their specific issues. Another master is the unit tests. A third one is other packages—that is if the class is part of the “package API”.

    Haskell solves this by making “module APIs” explicit. Modules have to declare what is visible to the outside world (default: everything). Any code on the inside is visible to all other code, alsways (in one file/module). So in Haskell there is no need to “tag” every single atom with public/private/whatnot.

    In any case – simplicity will be part of the solution I am certain.

    3. Yeah I’ve gone down the code-generator-road more than once being “locked into” C# for the last few years. But I don’t disregard the code-generator-road very much (but I would like better support for it inside Visual Studio .. it is kind of hackish to get it to work slick in that environment!).

    One positive side of code-generator-road is that the “main language” could be kept simpler.

    4. Agreed. I like pythons attempt - doctest - where the documentation of classes/methods also serve as unit test code.

    5. Yeah while I do see where you’re coming from (Haskell monads etc. - been there to) - and having TDDed my way out of more than one dependency jungle myself—I can see this in the future too. But it is a distant future.

  2. Avatar
    Matt about 2 hours later:

    How much of this list is covered by F# ?

  3. Avatar
    anonymous@linux.org.ru about 2 hours later:

    Please, kill this squalid language!

  4. Avatar
    Alex about 5 hours later:

    Echoing Matt – F# is it (IMHO)

  5. Avatar
    Mark Lee Smith about 5 hours later:

    I’m with anonymous.

  6. Avatar
    pmf about 7 hours later:

    It’s also about time that languages attempt to solve the problems that dependency injection addresses.

    I don’t see how this can be done and would be interested what you have in mind. I know the way the Java-world addresses DI (using ad-hoc XML-configuration languages, as for example Spring does) is not really very appealing. Switching to a dynamic language would allow one to specify dependencies more easily in the regular host language, but apart from that, I don’t see any possibilities to simplify the mechanics of DI (or alternatives). I’d appreciate very much if you could elaborate this a bit more.

  7. Avatar
    oopjosh about 10 hours later:

    F# is nothing more than a complete copy of OCaml, which is derived from Caml and SML. Their pattern matching/type deconstructing is really nice!

  8. Avatar
    Tore Vestues about 11 hours later:

    Compiler extensibility is king IMHO. Take a look at the .Net-language Boo. It’s an enabler for productivity. And if there is one thing our industry really need, it is tools for increasing our productivity.

  9. Avatar
    William Pietri about 14 hours later:

    Fabulous post, Michael. You have awakened a variety of now-suppressed desires; please forgive the length. I want all of what you suggest (except perhaps #4; I’ve not tried it yet), and more!

    Regarding #1, I’d love to see that be a pervasive principle, so much so that common objects (like List and Set) are immutable as well, requiring extra work for mutability. And that in general, all the language libraries only give you mutable things when it’s required.

    Related to #2, I think defaulting to privacy would be a great start, too.

    As to other things I’m after:

    Something I’ve always wanted is a better focus on, for a lack of a better term, run-time self-description. I’m forever figuring out how to wedge logging, state description, and diagnostic context data in to programs in a way that’s both pervasive and not absurdly obtrusive. I would love it if a language took that seriously.

    I’d also be delighted to see first-class support for refactoring, especially of APIs. I want more freedom to refactor without making giant messes for other people. I want the software equivalent of the web-server redirect baked in, so shims and adapters for old APIs are easy, consistent, and managed mostly automatically. And I’d like to be able to easily provide API users with a set of automated transformations that bring their code as up to date as possible, with notes on where they need to fix the rest.

    Related to that would be serious library management. I don’t know how it is in MS-land (although based on DLL hell, probably not so good), but there are a lot of one-eyed Java programmers who have been forced to stab themselves after accidentally glancing at an explanation of how Java classloaders work (or, more accurately, don’t).

    As long as I’m dreaming, that leads me to another eternal absence: a plausible story on addressing the various common use cases for a mainstream language. The reason that there are almost no Java command-line programs, for example, is because Java puts every barrier possible in the way of that. Difficult to call from the command line. Poor argument handling. Years without any easy way to distribute a runnable thing as a unit. Incredibly slow startup times. Version issues. And as I mentioned, library issues out the wazoo.

    Which brings me to what I really, really want: for the language designers, from the first week, to work in a big room with a lot of devs using the evolving language to make different kinds of shippable software. Without that, I think any from-scratch language is destined to either be a hodge-podge (coughperlcough) or something that’s great only in theory—and for a strictly limited set of theories mainly based on what’s wrong with the last generation of languages.

    And with a sense of mercy for your audience, Michael, I’ll stop here, as it turns out I’ve been holding things like this in for years. :-)

  10. Avatar
    Olof Bjarnason about 16 hours later:

    I’ve had this “Zero:th argument idea” floating around for some time.

    When a thread of execution enters a method, it not only gets hold of argument one to N, but it also gets hold of a “namespace of types+functions”—the whole context it executes in.

    For example:
    void MyMethod(int a, int b) {
      // in here, we can use ANYTHING
      // for example, we can use fopen,
      // printf or whatever is available in the
      // "total namespace" at this point.
    }
    

    I’d like to call that “total namespace” the “Zero:th argument” to the method:

    void MyMethod(0th context, int a, int b) {
    // context available here - it is the zero:th argument
    }
    

    Much of my experience from testing comes down to what Michael describes as dependency injection – that we make available a small context to the method under test, so that we can sense the effects the method has on that context.

    And assume the method will not be using fopen() and friends—something we really cannot “test us out of” in any way I can think of.

    What if the next mainstream language made the Zero:th argument explicit instead of implicit? So that you had to specify which “things you were to use”? And then the unit test code could override the meaning of that context-content, just as we use dependency injection today?

  11. Avatar
    Nirav Thaker about 18 hours later:

    One more addition to wish list: Integrate all concurrency primitives with language constructs. We don’t need N libraries/frameworks with N concurrency abstractions. IMO, this is essential to promote uniform concurrent programming.

  12. Avatar
    Nirav Thaker about 18 hours later:

    One more addition to wish list: Integrate all concurrency primitives with language constructs. We don’t need N libraries/frameworks with N concurrency abstractions. IMO, this is essential to promote uniform concurrent programming.

  13. Avatar
    me about 21 hours later:

    It’s a bit different, but Oz ( http://en.wikipedia.org/wiki/Oz_(programming_language) ) has many of the characteristics that you list.

  14. Avatar
    Ryszard Szopa 1 day later:

    Looks like Clojure has most of the features you’d like to see (except of the I/O separation). Making multithreading easier ootb and playing very nice with Java are a bonus.

  15. Avatar
    Ben Dyer 6 days later:

    Two things I’ve been thinking I’ve wanted in a language for a while are:

    1. left to write assigning so I can write:

    (do this) (assign result) (to this); eg:

    myObj.calculateNumber() => someVariable;

    I don’t know, I think in left to right, so I’m constantly finding I have to Jump around my line, I write the end and then have to go back and write the start. If I could have a standard new() method as a constructor and a ==> to mean something like ‘var’ in C# then I’d love to able to write a line like the way I find my self writing them in reverse now:

    MyClass.new() = => myObj;

    2. I’d like my methods to be able to accept parameters ‘mid sentence’. so for example I could write a method like

    public string Replace(string find)With(string replace){}

    so I could write code like:

    myString.Replace(“old”)With(“new”) => newString;

    Cheers,

    Ben.

  16. Avatar
    manuel about 1 month later:

    @Ben:

    You can get similar readability with named parameters:

    myString.Replace(“old”, with=“new”)

Comments