Javascript is poised to be the most ubiquitous programming language in the world. It's in web browsers, and now it's making inroads on the server side. The latest engines are able to optimize it to machine code, and its single-threaded operation with asynchronous callbacks allows efficient, multitasking code in a simple paradigm.
But Javascript is a complex language that's not easy to learn, and even harder to master. Also, some things (such as waiting for several objects to be filled asynchronously, and then using them as soon as they are all available) do not have native support in Javascript, requiring tricky libraries for control flow.
Thus I feel there is a need for a simpler language that compiles into Javascript. That would give us the best of all worlds -- a language you can teach to new developers, which encourages good programming practices, and everything written in this language would be easy to understand by others. I'd call the language Q, and here are some properties it would have:
0. You should be able to nest block comments
We are starting small. This is a minor gripe. Basically I should be able to comment out chunks of code which already contain commented-out code, in a non-smart, without causing the compiler to barf.
1. All keywords would be one letter
Benefits:
People know there are no more than 26 keywords, and make sure to learn them all. Learning the vocabulary is literally as easy as learning (a subset of) the alphabet.
You never have to wonder if you are using a reserved word
Terse and efficient to type code, more compact for reading purposes
Encourages variables to have meaningful names. Perhaps i,j,k would not be keywords, but f would be, for example, the keyword "function" in javascript.
Implications:
Built-in types would be capital letters. For example:
var1: S.n // a new string
var2: N.n // new number
2. Like Python, indentation would begin a new code block. Line breaks would end a statement, unless the parentheses are still open.
Benefits:
No more arguments about brace style, easier-to-read code
No more forgetting to match braces, or even worse "})" vs "}" -- admit it, you had lots of mistakes like that
No more forgetting semicolons, They can still be used if you wanted to place more than one statement on a line. But that would be rare.
More friendly to IDEs. Right now an IDE has a terrible time trying to guess which brace is the ending brace, or whether your statement is really going to the next line or not. When the language is easy for the IDE to understand, it becomes your friend and helps you catch all of this as soon as you hit return.
Implications:
Parameter lists containing functions defined inline would continue by means of comma-first style, which although it takes some getting used to, helps you catch many more mistakes.
Example:
obj.message(param1, f (var1, var2)
alert(var1)
alert(var2)
, f (something)
// another function
r 2 // returns 2
)
3. Use : for assignment, = for comparison, == for javascript's ===
Benefits: (once you get used to typing in the language)
Never make the mistake of making an assignment when you meant to compare, by forgetting to type an extra =. This error is hard to spot.
Assignments are easier to identify. This language also re-uses : for making sure something is of a certain class (thus, a: I instead of "int a" in C.)
Strict-equality is slightly easier to type.
4. Use : and messages to do most of the work.
Benefits:
Function calls, the keyword "new", and even exceptions are just messages. Kind of like in Ruby. Except here, messages trigger events and you can add hooks before and after an event on any object.
var1: I // declare var1 and assign it to be an integer
var2: I.n(32) : 5 // new 32-bit integer equal to 5
var3: MyClass.n(var1, var2);
var4: MyContainerClass.n(MyClass, 2, 3); // some more cool stuff
f something(cool:I, fool:MyClass)
// do something in the function
r 2 // return 2
in this last example, the function was expecting an integer and a MyClass instance, respectively
5. Loops are discouraged, instead we will have the .each(function(i, obj) { }); construct, except with the keyword x
Benefits:
Loops can suck -- there are problems with closures that bite javascript developers in the butt, there are concerns about how many times the loop's condition actually runs, etc.
The container classes should encapsulate their traversal logic, the calling code should be able to "not know" what the logic is, and just use a standard mechanism. So we can still have while() and until() constructs, but we also achieve the .each by sending a message to a container instance, as follows.
Example:
In the following example, we are looping over an array of even numbers 2 through 198 inclusive:
([1..100]*2).x (index)
//do something with this index
//in this code block
someCollection.x (obj, key)
// etc.
6. Exceptions are just messages to an object which may be forked from an earlier exception handling object.
Benefits:
Javascript is bad with catching exceptions when they are thrown a callback passed to an asynchronous function call. Your call stack has long since gone (since your call was asynchronous) and no one is around to catch your exceptions.
There are fewer concepts to worry about. Exceptions are just messages now. The "catch" and "finally" clauses are just methods of the exception handler object.
This would also lead to a nice paradigm for error handling, which is flexible enough to handle error callbacks AND catching exceptions.
Example:
// here we call some function, passing a callback and catching exceptions
foo.bar(f ()
// this is some callback defined inline
, callback2, // this is some callback stored as a variable
): {
'Some exception type 1': f (param1, param2)
// something going on
, 'Some exception type 2': errorHandler
, 'Some exception type 3': errorHandler
, '': f (param1, param2)
// this one catches all other exceptions
}, f()
// this is the finally clause
// or rather, the finally handler
We throw exceptions manually with the following
// throw an exception
e[exceptionMessage] (params)
e['Some exception type 2'] (params)
e.myException(params)
Notice what happens here. This syntax is consistent with just sending messages. You send messages with obj.message for messages whose names you know, or obj[message] for messages whose names are variables.
When exceptions are thrown (whether due to a division by zero, or manually with e.msg()), we go up the function stack until we find an object capable of handling this message.
That function call syntax,
foo.bar() [: someObject [ , someFunction ]]
is what makes this possible. It takes the "invisible" object that is listening for various messages that are thrown, and extends a copy of it with someObject, before calling the function. Then, the "t" keyword represents this object, except that calling a method in it actually unwinds the stack first.
7. It would also not have case statements and enums. These can all be done using messages. Just like loops, case statements and enums encourage breaking encapsulation... cases and enums should be done with messages, and the calling code shouldn't be responsible for having to know what the messages are, especially if it's just a container "passing along the message".
8. Finally, it would be nice to build in support for some "re-entrant functions", where you could call this function repeatedly while it's going off to do what you wanted, and it puts the calls in a queue, and then calls all the callbacks once the asynchronous processing has been completed. It would also support caching (memoizing) the result of the function call and throttling the use of resources on some semaphore.
I'm not sure how to implement this one, but it would be nice to have as a language construct.
-------------
Wow, this post is long, but I had some of these ideas around for a long time and I have no time to implement something like this. I thought that at least I can propose this to the hackers on here in the hopes that some of this might find its way into an actual implementation someday :)