Early Decisions All Language Designers Will Regret

schwern on 2005-04-08T23:04:41

I recently have to touch Javascript and am horrified at the lack of a require/include function. Now I find out it doesn't have namespaces either.

FOR FUCK'S SAKE PEOPLE! Did these guys all sleep through the last 30 years of language design? If I had to enumerate the most important programming innovations in the last three decades namespaces and include would be near the top of the list. This isn't something "fancy" like OO, these are the basics of MODULAR DESIGN: the thing which separates spagetti code from real code.

Javascript isn't the only one to have made this mistake, not having namespaces. Lua comes to mind as well as earlier versions of Perl and PHP. Perl regretted it. PHP is still regreting it as they struggle with their new namespaces. Every C programmer which writes a header file regrets it. Lua is just now starting to regret it as they hack further and further around the limitation.

So maybe its not so obvious. Maybe we need a list. A list of early decisions which all language designers will eventually regret. And take it from the Perl programmers... we know. Oh god we know.

I'll start with the obvious ones. I'm no cross-language expert so my examples may be out of date or just plain wrong.

  • No namespaces. (C, Lua, Javascript, early PHP, Perl 4)
  • No way to include a file. (Javascript, very early C)
  • No lexical scope. (PHP still screws this up I think)

And throw in some less obvious ones.

  • No anonymous functions. (Java and to a certain extent Python)
  • Everything's an object... except for the built in data types. (Java, Perl 5)
  • Treating IO as unimportant or no IO at all (C, Java, Javascript)

And some controversial ones.

  • Not requiring variable declarations. (Perl 5)

Add your own! Point out more violations! Its fun!


static typing

mary.poppins on 2005-04-09T00:08:52

Here's another controversial one:

    Static typing without type inference

If you're going to make a new language, please make a type-inferring version of C. kthxbye

Re:static typing

schwern on 2005-04-09T01:22:47

Could you give an example of a language which avoids this regret?

Re:static typing

ziggy on 2005-04-09T02:25:47

Haskell's static typing does pretty good inferencing.
$ ghci
Prelude> let sq x = x * x
Prelude> :type sq
sq :: forall a. (Num a) => a -> a
That is, the function sq is defined for all values a, such that a is an instance of the type class Num. Remember, Haskell is strongly typed, and this function is defined on all kinds of numbers (machine integers, bignums, floats, doubles, complex, etc.). Furthermore, it will be defined on all types you define that are also instances of Num.

This is a contrived example, but it does demonstrate type inference. That is, it avoids the Java problem where a function like sq needs to be redefined for every numeric type you want to square (primitive numbers and user defined numeric types). All with a single definition.

Re:static typing

mary.poppins on 2005-04-09T02:43:41

Statically typed type-inferring languages include ML variants (SML and OCaml being the most common), as well as Haskell and Clean.

All of these have entries posted at the shootout.

None of these languages are suitable for low-level programming (kernels, databases, etc.), which is why we need a type-inferring version of something more like C.

Perl 4 had namespaces

brian_d_foy on 2005-04-09T01:08:10

Perl 4 had packages, which allowed pudge to write the D'oh module. :)

Re:Perl 4 had namespaces

schwern on 2005-04-09T01:20:37

Oh yeah. When did "package" get added?

Dynamic Features

ziggy on 2005-04-09T02:47:55

You missed three key features:
  • No Closures (Tcl messes this up; I think Python does too...)
  • No eval (C, C++, Java; makes it much harder to do macros and code generation...)
  • No variable interpolation (printf is just soooo lame)
Now, with interpolation and eval, you can fake closures, but it's a royal PITA. Here's the classic make_adder example in Perl:
sub make_adder ($) {
     my $i = shift;
     return sub {
         return shift() + $i;
     }
}

$add1 = make_adder (1);
print $add1->(2);  ## 3
Here's a simplistic translation in Tcl. Procs aren't first class, but you can create new procs dynamically. This code is broken, because Tcl has lexical variables, but no closures:
proc make_adder {procname value} {
    proc $procname {n} {
        return [expr {$n + $value}]
    }
}
make_adder add1 1
add1 2  ## can't read "value": no such variable
But you can work around that problem fairly easily with interpolation:
proc make_adder {procname value} {
     proc $procname {n} "return \[expr {\$n + $value}\]"
}
make_adder add1 1
add1 2
It'd be a lot better if you could make an accumulator (i.e., true closures), but even without, you can still get a decent boost in power with dynamic code generation.

I'm still on the fence whether or not you absolutely need macros. If you can construct classes, methods, and closures at runtime, you can fake it pretty well. And you can even work around the lack of closures if you absolutely must. But without all three of these features, you might as well be programming in C.

(Yeah, [expr ...] is an wart in Tcl, but them's the breaks.)

Re:Dynamic Features

schwern on 2005-04-09T23:06:52

I think much of that can be summed up as "allow dynamic code generation" and "have code with some data attached". And to be able to do these things easily. Java, for example, limps along using pre-processors (IDEs, AspectJ, ...) for the former and anonymous classes for the latter.

Re:Dynamic Features

ziggy on 2005-04-10T19:36:10

Respectfully, no it's not. You really do need closures. Hey, it's 21st century -- get with the program! ;-)

Reducing this down to "allow dynamic code generation" and "attach data to code" is a false economy in specification. Once you have true closures (which imply lexical scoping), a whole new way to code is opened up for you. Instead of writing ridiculously long classes to, say, find files, you can have small simple classes that are responsible for the algorithmic structure of finding files and nothing else -- selecting symlinks, directories, writable files, whatnot is all your responsibility to handle in a callback.

You can punt with eval if you don't have closures, but that's like using two liters of grain alcohol instead of a shot of novocaine before doing minor dental surgery. Eval and interpolation turn that grain alcohol into something more palatable, like Irish Whiskey, but it's still fundementally the wrong solution.

Limping along with pre-processors and static code generators is still a bad problem. You might as well be programming in C at that point. That strategy recognizes the need for code that writes code, but ignores the fact that you really need to be alble to do that at runtime (like, when you need to construct a callback to File::Find, and maybe something with persistent state to select the first ten results).

Re:Dynamic Features

schwern on 2005-04-10T21:45:05

I think we're having a violent agreement.

By changing the wording from "have closures" to "easily attach data to code" (with the implied "at runtime") I was attempting to restate the problem as a problem rather than as a solution. Closures are a solution to the problem and you've layed out the problem quite well. I would rather not assume that closures are the only way to do it. Or eval. Or pluggable functions (*foo = \&bar). Maybe there is a way to do it efficiently and concisely with anonymous classes, dunno.

Re:Dynamic Features

ziggy on 2005-04-10T23:58:25

Hm.

As you stated the problem above, there are some design decisions that are fundementally wrong in language design today. Like no namespaces, no way to include files, etc.

"No Closures" is as big a design flaw as "no anonymous functions", "no eval" and "no interpolation". You can find all sorts of ways around that problem, but fundementally, there's no good reason to not have closures. Rephrasing that, there's no defensible reason for asking your users to jump through hoops to achieve the same result (like Python's hackish way of using anonymous objects with a __getattr__ method).

I think you're focusing on the impact rather than on the language feature. Yet you started with a list of language features.

From what I've seen elsewhere, wiggling around the problem of closures by making the moral equivalent of a closure available is just as much of a problem. Even Smalltalk has closures, even though they call them "anonymous blocks". Whatever you call them, they're pretty much a requirement today.

Re:Dynamic Features

ziggy on 2005-04-12T18:26:47

Re-reading your comment, this stands out:
Maybe there is a way to do it efficiently and concisely with anonymous classes, dunno.
Anonymous classes are a form of closure. It's how Java fakes closures in particular, and it's a cleaner solution for some problems. (You could define anon classes with dynamic scoping rules a la Tcl, but why?)

The issues are very closely related, but not identical.

If you say no to closures and yes to anon classes, you're really saying no jam! and more orange marmalade!

Even more unbelievable

vsergu on 2005-04-09T04:26:22

No user-defined functions -- ColdFusion, until version 5, I think. That's clearly more than enough to disqualify it as a real language,

include

pne on 2005-04-09T05:53:51

I don't know... when I think about how to use functionality from several different pieces of code in C, I don't think of "include"; I think of a linker. Including another C source file in another strikes me as a bit odd.

Though perhaps thinking static inclusion is not quite the same - Turbo Pascal had "uses foo, bar baz;" lines which were directives to the "linker" IIRC (though there was no stand-alone linker, I think; it was a compiler-and-linker all in one, so you couldn't link TP code to, say, C code, though the other way around worked).

Perl 6

malte on 2005-04-09T12:16:40

Everything's an object... except for the built in data types. (Java, Perl 5)

Doesn't Perl 6 make this mistake, too? (I'm thinking of the int Type)

I'm re-reading the Smalltalk 80 book (last time I read it was about 10 years ago). Its really amazing how much they were ahead in 1980 compared to the state of most languages in 2005

Re:Perl 6

schwern on 2005-04-09T21:18:23

Doesn't Perl 6 make this mistake, too? (I'm thinking of the int Type)
Don't think so. I sure hope not. AFAIK Parrot doesn't make that assumption so there's no implementation reason why Perl 6 would.
I'm re-reading the Smalltalk 80 book (last time I read it was about 10 years ago). Its really amazing how much they were ahead in 1980 compared to the state of most languages in 2005
Surprise! And people wonder why they should learn Smalltalk. If more folks learned Smalltalk instead of C++ in the early 90s I think object-oriented programming would be a whole lot farther along now. Fortunately, Matz did learn Smalltalk and thus we have Ruby.

Re:

Aristotle on 2005-04-10T22:54:52

You can do namespaces in Javascript. And there is no include in the language, because <script src="..."> does that for you. Javascript is actually a pretty nice language. I far prefer writing JS over PHP, even over Python (though not as decidedly).

iframe hacks

itub on 2005-04-11T18:55:29

I decided to play a bit with iframes for emulating JavaScript includes and namespaces, as you can see in my journal.

No regexes, no decent string handling

roeland on 2005-04-14T08:59:15

Anyone who's ever tried to do anything nearly useful with XSLT must have hit the same wall (no pun).

The very least you'd expect in a language is taking a substring at fixed position, and locating and optionally replacing a substring. But just how many C programs that do some form of string processing do you know that do not link against some regex library? Right.

javascript as a general purpose language

spike0xff on 2005-08-04T01:13:04

So Michael... Read a lot of your comments today on js, including the thread about 'include'. Are programmers literal-minded or what? I've recently discovered javascript, and I am so charmed by its elegance as a language. I'm an old Lisp fan, but a long-time C/C++ programmer, so... I'm writing a javascript-to-C++ translator. For fun, and as a way to understand javascript. The idea is to use the local C++ compiler as a backend, link to a JS runtime, and be able to get a native .exe (on Windows), so I can do tools and hacks in a language I like and respect. Obviously I'll have to add an I/O system. (I was thinking of something like stdio ;-)
The Konfabulator http://www.konfabulator.com/info guys have an include function in their javascript dialect, and what looks like a very slick way to import COM objects. So all in all, javascript seems to me (and I am just starting out) like a sweet way to cook up useful applications quickly. Am I insane?