Are you guilty of subconsciously regarding ’scripting’ languages as inferior? Do you think real programming means using C, C++, C#, or Java? Does your conscience accuse you of laziness when you feel tempted to use a truly high-level language for your app?
If so, don’t be too hard on yourself. It’s unfortunate, but the computer industry is more prone to group-think than any other. The inherent complexity requires us to take other people’s word for most things… Besides, it would seem that scripting languages are for scripts…right?
It’s true that scripting languages are fantastic at gluing libraries and applications together quickly. But they’re not just for scripts. Truthfully, any language can be used for ’scripting’… But sophisticated high-level languages that excel at the job have an tendency to get branded. Ironic, eh?
Javascript, for instance
As a language, Javascript is one of the most high-level languages in existence. It was designed from the ground-up for object-oriented development (although with a quirky syntax). Its dynamic, reflective, and functional nature allows excellent code reuse (mix-ins, events, delegation, aspect integration), and the excellent array and dictionary syntax makes the language suitable for representing data and expressing domain-specific logic. It’s easy to unit test, and there are great IDEs for it (Aptana, Flex Builder).
Javascript is multi-paradigm. It is imperative, procedural, object-oriented, functional, and dynamic. If you want to write procedural garbage and use global variables, javascript will let you do it.
Javascript code you see on the Internet tends to be poorly written, buggy, and ugly. It’s not because the language is bad – it’s because few people learn how to use it properly. Developers copy and paste bad javascript, then write more code just like it. Thankfully, this trend is starting to reverse, and we’re seeing good javascript on a wide variety of sites. High-quality libraries like jQuery, Mootools, and YUI are showing developers what Javascript *should* look like, and how powerful it can be.
Javascript doesn’t get in your way, even if you’re doing something incredibly stupid. True, the syntax for making namespaces and classes could be improved, but the inherent flexibility lets you change even this – you can write defineNamespace(), defineClass(), and addMembers() functions if you so desire. Sometimes this freedom can get to your head, so remember that discipline is as important as ingenuity when writing code.
Javascript is a very high-level language, with excellent support for a variety of paradigms. It’s built into the JVM, every modern browser, and has over 20 server-side implementations.
The fact that it is possible to write javascript applications that run on a range of browers (implementations) is really amazing. Try writing an application that can run on Windows, Linux, and OS X without having targeted versions of the executable or the executing framework. Like everything else, javascript is easier in a consistent environment, like an AIR, Mozilla, or HTA app… Don’t blame the language for the framework. Don’t blame Javascript for the DOM.
On the desktop
The XML+Javascript combo is very suitable for large applications. Tying libraries together and defining the behavior of an application is definitely a job for a high-level dynamic language.
Here’s just a few desktop apps that use the Mozilla (XUL+Javascript) platform: Firefox, Flock, SeaMonkey, Thunderbird, Chatzilla, Nvu, Sunbird, ActiveState Komodo (my Python IDE), Joost, Miro, and Instantbird.
HTA (Microsoft HTML applications) have been around forever (2001?), and are used extensively within Microsoft products. I’ve written a few also, for windows-only tasks. They’re just HTML files with a .hta extension and local filesystem permissions. If only IE had debugging….
Adobe AIR is gaining immense popularity, and there are already thousands of apps out there. Need I say more? Javascript has proven itself a mature, capable language, suitable for big things.
Thank you, javascript
We have a lot to thank Javascript for. It’s a vector for single-paradigm developers to expand their horizons with. It’s pushing the performance boundaries of dynamic languages. Every six months a new virtual machine seems to be leapfrogging 50% over the previous leader. Smart just-in-time compilation is bringing incredible speeds to javascript, and with tiny initialization costs.
Python, for instance
Python is a little less implicit that Javascript, and follows the DRY principle very closely. It is designed to enforce readability, wheras Javascript relies on programmer discipline (which is needed anyway).
Python is used as a scripting language in lots of heavy-duty applications like GIMP, Inkscape, Blender, Maya, PSP, and… Linux. But it’s not named PythonScript, thank goodness, so the ’scriptiness percepection index’ is much lower than javascript.
Lots of popular stuff is written in Python, like BitTorrent, Trac, Bazaar, Mercurial, TinyERP, ClamWin, Yum (linux package management system), Zope, and Django (my favorite). There are actually some drivers written in python. It’s pretty speedy.
Python is probably a good balance between power and guidance. It supports a variety of paradigms, but has great default behavior and tends to guide you towards doing things one way. Python is reflective, object-oriented, dynamic, and functional much like javascript. It’s a little stricter, but not enough to really annoy.
Ruby, Lisp, and Perl
I’m lumping these three together since I don’t have much experience with them. I’ve been studying them, though, and all three seem very powerful and sufficient in their own way. All three are incredibly popular, with hundreds of thousands of apps in the wild.
My defintion of high-level
At one time C was considered a high-level language. At one time C++ was considered a high-level language. Not too long ago I considered Java a high-level language.
In my opinion, a true high-level language must allow programming with the following paradigms as a minimum
- dynamic (Any member can be overridden, objects act like dictionaries)
- reflective (Members are enumerable, executable, and can be referenced by string name)
- functional (Functions are first-class objects, with members)
- object-oriented (Functionality can be organized into objects, which can have instances containing state)
- Imperative
It must also have these features
- In-line documentation
- Built-in list and dictionary syntax.
- Variadic functions
- Easy ‘undefined’ and null checks
- Exception management
- Duck typing (optional explicit typing is OK)
- Automatic memory management
- Closures, anonymous functions
- Good list, dictionary, string, math, and date libraries.
I don’t believe the future is in contract-based programming. The contract safety that interfaces and classes provide is shallow and of questionable value. It is behavior, not integration that typically causes problems.
A far better approach is to use a language that facilitates defensive programming and easy unit testing.
You might be missing out on some good statically-typed languages here. Consider Haskell, Scala and F#, all statically-typed, but with (varying) type inference, so the types don’t have to get in the way.
Where they don’t support something, support can be added. For example, Haskell doesn’t support OOP out of the box, but you can add that as a library (I believe such a library exists).
None of the languages you listed satisfy one of your checks for high-level language; implicit typing. They are all untyped languages.
Sorry, meant duck typing – good catch.
Type inference does mitigate the problem slightly (It’s appearing in many static languages now), but here’s the question:
If it walks like a duck and quacks like a duck…. Can we let it in with the other ducks? Or does it need an license?
When it’s so easy to check to see how the duck walks and quacks… Why deal with the overhead and limitations of the license?
In my opinion, contractual programming is a case of premature optimization on a severe scale.
You can have such quackery in a static language, for example, Scala’s structural typing.
scala> def doDuckLikeThings(duck: { def walk: Unit; def quack: Unit }) = { duck.walk; duck.quack }
doDuckLikeThings: (AnyRef{def walk: Unit; def quack: Unit})Unit
scala> class Swan { def walk = println(“I’m walking”); def quack = println(“waaak”) }
defined class Swan
scala> doDuckLikeThings(new Swan)
I’m walking
waaak
A potential problem is that there’s no real way to know whether the method means to the object’s author what it means to you. aBath.run is quite different to anAthlete.run. More importantly, not having types available makes it hard to think in types, i.e., it makes it hard to reason about your code. Types can express a lot more than you might expect. They can make unit tests unnecessary (or at least smaller), and in the case of QuickCheck/ScalaCheck they can improve unit tests no end.
> A potential problem is that there’s no real way to know whether the method means to the object’s author what it means to you. aBath.run is quite different to anAthlete.run.
Can you say you have ever encountered this in a real application?
>More importantly, not having types available makes it hard to think in types, i.e., it makes it hard to reason about your code.
I don’t think it makes it harder to reason about your code at all. Static typing provides a false sense of security just like compile-time checks do. Compile-time checking is really just a productivity feature. Static typing helps catch a small class of typos, but causes an incredible amount of programmer overhead.
> Types can express a lot more than you might expect. They can make unit tests unnecessary (or at least smaller), and in the case of QuickCheck/ScalaCheck they can improve unit tests no end.
I’m no Scala expert – could you provide a real-world example of this?
>> A potential problem is that there’s no real way to know whether the method means to the object’s author what it means to you. aBath.run is quite different to anAthlete.run.
> Can you say you have ever encountered this in a real application?
I said ‘potential’.
> Static typing provides a false sense of security just like compile-time checks do.
Actually, types prove that certain categories of runtime errors are impossible. They can do much more than catch typos. It’s a shame that most people’s experience of types is from C, C++ and Java, because I can’t think of worse implementations of it. People have a wider view of untyped languages than of typed ones.
For example, if I am working outside an IDE I can do a refactor, and generally the code won’t compile until I have fixed all the resulting problems. This is even true within an IDE, as IDEs don’t support every refactor you might want.
Support for typing in the language helps me, a programmer who thinks in types, because it helps me to keep track of things. If I’ve made an error in my thoughts, I’ll get a compile error.
> Compile-time checking is really just a productivity feature.
That’s a non-argument. You could argue that anything above assembler is a productivity feature.
> Static typing helps catch a small class of typos, but causes an incredible amount of programmer overhead.
I hope I’ve addressed the typos bit above. Whether it causes programmer overhead depends on the programmer and the type system.
>> Types can express a lot more than you might expect. They can make unit tests unnecessary (or at least smaller), and in the case of QuickCheck/ScalaCheck they can improve unit tests no end.
> I’m no Scala expert – could you provide a real-world example of this?
This is from my current codebase; it’s a ScalaCheck test inside a Specs specification:
“Converting an EventsCGI to a String and back” should {
“not be lossy” in {
property { e: EventsCGI => { EventsCGI.fromURL(e.toString).toString == e.toString } } must pass
}
}
ScalaCheck will generate up to 500 EventsCGI objects (according to rules I defined elsewhere), and run this test on them. When it finds a failure, it will try to ’shrink’ the failure case, meaning that it generates more EventsCGI objects but with smaller values and runs the test again, so that the developer gets a small value. I can’t really paste more than that, which is the downside of asking for a real-world example. It also doesn’t really show off type inference, but the ScalaCheck docs should do that.
I missed something in the ScalaCheck example. The ‘generator’ for EventsCGI objects is not actually passed into the property method, so to implement this in Ruby you would probably have to have some kind of map of ‘types’ to generators, which would be a global mutable data structure, whereas in Scala it’s an implicit parameter. The type system is helping here by choosing the correct ‘implicit value’ from the surrounding scope.
Well, I think we both agree that the C/C++/Java/C# type system is an utter failure… And while I had previously thought of Scala as being an extension of the same bad idea, your examples have convinced me that Scala is actually worth looking in to.
Strangely enough, from looking at Scala’s feature set you might mistake it for one of the dynamic languages. I’d assumed that (like C# and Java), each of those ‘dynamic’ features would introduce syntactical overhead since the behavior isn’t inherent to the language approach.
I’ll probably have to try a small app in Scala to see whether the type system gets in my way or not… If structural typing is the default, then Scala might just make it onto my ‘list’.
Scala is worth looking into, but it isn’t a silver bullet. There are a number of syntactic oddities. F# looks like it’s a more consistent language (with similar features), but I haven’t looked into it enough, not least because I don’t normally run Windows.
C# is actually a bit of an improvement, e.g., var s = “hello”; s is typed as a String. And C# has talented language designers, so it is certainly one to watch.
The point really is that typed languages don’t have to have unreadable code. It’s just that the mainstream ones tend to, though this is likely to change, slowly.
Why does the “perfect” language have to have everything? Can’t programming langugages have their focus and capability? In the 1970s, PL/1 was the language that “had it all” – the best of Fortran, COBOL, and systems programming. Maybe we should also add to your perfect list – database (SQL/LINQ), XML as a fundamental datatype, Contracts, Mocks, parallel/multi-core types and contructs, and anything else that programming language research comes up with in 2009 and beyond. Focused languages and language interop seem to be a good way forward.
Well, I think the problem we’re facing today is that modern web apps touch *everything*. They are receiving data and javascript commands, calling web services, talking to the database, calculating data, and rendering tons of XML.
If you’re doing a focused command-line application, you may be fine with pure functional code or pure OO code. But most modern apps aren’t just dealing with object simulation or calculations. They’re working with lots of data structures, and therefore need dynamic objects and reflection. They need to manipulate sets of data easily, and do concurrent programming. Functional programming is invaluable when working with data or making algorithms extensible.
PL/1 suffered from committee design.. But I agree, languages have always been far ahead of the industry as a whole. Think SmallTalk and LISP. LISP never went really mainstream until it got put in Java’s syntax as Javascript. Programmers are hard-headed.
Regarding SQL/LINQ and integrated XML… Those are both easily implemented with dynamic objects and dot operator overloading (Present in most ‘dynamic’ languages). No need for a language addition there.
Regarding concurrent programming… I see a hybrid approach here. Almost all ‘concurrent’ programming is best expressed in the asynchronous begin/end form. For actual calculations (vs. I/O, audio, video, or web-service calls), I like the philosophy of clojure.
If you actually read my article, you should already know my opinion on contract-based vs. test-based programming.
What languages do you currently use on a day-to-day basis?