As Languages Evolve...
naph writes "It seems that as programming languages have developed there has been a steady increase in the level of abstraction they use. Early languages were all very low-level, but successive generations have become higher and higher. Is this trend going to continue, or do you think we've reached a kind of happy medium between power and abstraction? Would developers prefer higher level languages, or is the direct control of things good? I was just wondering what other developers out there thought of this."
As long as the compiler is efficient and very good at optimizing, more abstraction is OK. But if abstraction comes at the price of too much speed (Or executable bulk), then the compiler should not exist.
The entire point of computers is to do things much faster than we can do them manually (or even make things possible that weren't before).
By making programming easier and faster with higher-level languages, this contributes to that goal.
There IS a trade-off in speed by doing things at such a high level versus, say, machine language, but considering the scope of most apps these days, it wouldn't be economically viable to create everything that way. Optimising certain bottle necks in low level languages is probably about the only common use programmers will have for low level stuff in the future, except for certain special cases or very small applications.
If you have a project that needs super-duper optimization, it might be better to concentrate on improving the compiler's optimization rather than writing your app in a low-level language. Keep in mind you have to maintain your code!
It seems to me that any language, be it Java, C++ or plain old C will become more abstract on their own as people begin to use libraries and reuse classes and methods. Once someone writes some basic classes, he will write classes which use those, and so on until the classes which he writes are many steps above the original class in abstraction.
I don't really think that its a trade off between power and abstraction. It's more a case of expressiveness vs efficency. All languages have the same power - as long they're universal and not some subset of a universal language.
Expressiveness is slightly different though, as you move up through the levels of languages; from machine code up to imperative languages like C++ and then up to functional or logic languages like Haskell or Prolog - you lose control over telling the machine how to do something and focus more on what it should do.
Potentially this gives the compiler more scope for optimisation and leaves the programmer able to reason about more complex systems. Yes you could write something like, say a datamining or visualisation app in assembly language. But how much more effort would it be than doing the same in Haskell?
These nicer abstractions actually make it easier and quicker to write more complex code (theres a hell of a lot less of it for a start). I would think that theres still a level higher that we could go that would give a useful impact in productivity. The holy grail of language research is an abstract specification of what a program should do, from which an actual program can be generated automatically. This would allow complex systems to be verified more easily (and correctly) which are the kind of qualities that you need to move software from a scientfic (artistic?) discipline into a mature school of engineering.
Hopefully that would lead to more realiable systems but comes back to my original point about efficiency. In the longterm it may be more efficient rather than less to use these levels of abstraction as the large complexity of the types of systems that we will be designing will stop anyone from 'coding them by hand'.
Slashdot: where don knuth is an idiot because he cant grasp the awesome power of php
Well, you're being clever or naive, I can't tell which. By faster programming, I'm guessing you meant that you can whip the code out quickly and efficiently. That's true. However, there are just some cases where a higher level language will cost you, such as Embedded Programming, or high performance drivers and software (e.g. the Detonator series of drivers from NVidia). If you're trying to push as much data around as physically possible in a clock cycle to get that extra 2 FPS, low level is the way to go.
With each level of abstraction, you, as a coder or designer, add overhead and unneeded to the code operations (check the assembler's output of a c++ program vs. hand-written assembler).
The human mind is still the best code optimizer out there today.
Michael C. Hollinger
When it comes to languages, the answer is use the right tool for the job. Low level languages will always coexist with high level ones.
As I recall from my software engineering class, programmers program at the same rate in lines of code regardless of the language (I believe IBM did the study in the 80's, but dont quote me on that). Therefore, programming languages SHOULD be more abstract to increase productivity. It also comes down to the "reinventing the wheel" factor. The more bug-free features/libraries we can stuff into a language, the more we can produce bug free code quicker. The only problem is of course that abstraction comes at the cost of speed. How much more enjoyable is it to program in java and not have to worry about cleaning up memory than say C or even assembly where everything is a battle. I dont know about you, but I would much rather type create_new_window() than worry about framebuffers and things of that nature. Hopefully this can be accomplished while keeping speed up and code bloat down
In addition, Stroustrup was correct in saying that a language affects the ways you think about a problem. His language (C++) is certainly expressive, if terminally ugly and wart ridden.
In addition, I think that there are plenty of counter examples to your assertion. Python and Perl are both (relativily) young languages that allow many degrees of freedom. Further examples would be ocaml, which is also quite dynamic.
cpu ------> abstraction ------> problem
As a result, the more abstract language is often less efficient for the computer to execute, but allows the programmer to describe the problem to the computer faster. That is, it makes him more productive, in the sense of...
productivity = features developed / times spent
Now,
As a result, the "sweet spot" in the tradeoff between programmer time and cpu cycles now is with more abstract languages than it was 10 years ago.
This is also why in the past the abstraction level of "mainstream" languages has steadily increased (machine language -> assembler -> macro assembler -> COBOL/FORTRAN -> modular/structured languages -> object-oriented languages).
This is also why I firmly believe the abstraction level will keep going up, through stuff like:
I also expect more new languages to have dynamic instead of static typing, which is also a way to attain higher programmer productivity (especially for refactoring) at the cost of compiler/runtime efficiency.
One more note: Before you argue against higher abstraction, please check if your line of reasoning could have been used as an argument for assembler and against higher-level languages. If so, maybe something is wrong with it...
Stupidity is mis-underestimated.
I think the hard tie to a single langauge for a project is slowly going away. I think you are going to see more and more projects done in 3+ langauges. You built your first revision in a scripting langauge (python), and have it calling your database (PL-SQL), then once most everything is working you will go in and check how it preforms. You profile the slow bits and port them over to a quicker langauge (C++) using a tool to help you tie it all together (SWIG).
I could have written this little made up example using (perl), (xslt+xml), (C), (h2xs) or one of a dozen other combos.
The power of using a scripting langauge as a major component of your project is you get rapid prototyping, and easy extendability. The advantage of using a lower level langauge is speed and "access" to APIs of hardware you might need. Why anyone would feel the need to limit themselves to one group is beyond me.
The progressive abstraction of computer languages slow to a creep a long time ago. OO has been around very a very long time, just not neccesarily in the form of C++. Essentially the CS world has settled on some mutual understanding the the range of abstraction around C++, Java, and Perl is a pretty good place to be depending on how OO and whatnot you want to be (and of course we will always have the ever-enduring C for simpler and systems programming), and we can't seem to come up with anything better that's got more useful abstraction than that.
There's been a dream of a useful and successful 4GL for many many years now, and from time to time someone claims they've done it, but it's a shoddy system that isn't flexible enough and too proprietary (comes with it's own crappy OS just for that programming language, etc). 4GL (4th generation language) is supposed to supremely abstract away the need for code altogether, or at least try to. In my idea of a proper 4GL, programming would consist of composing one well-structered XML document describing the objects your problem domain deals with, what they can do, and your business rules for dealing with them. It should be something a non-technical person who understands the business can write with a little help for a helper gui. From there 3GL (C++, Java) source code, GUI elements in whatever, middleware servers, database design and sql code, should all spring forth on it's own. But like I said, so far 4GL has been a pipe dream, we seemed to have reached a point where it's going to be very difficult to get much further without figuring out true-AI first, which is some ways off.
11*43+456^2
On the contrary, I believe it is you who is being clever or naive. Embedded programming, device drivers, etc. account for maybe one tenth of one percent of all source code written today. I fear that I may even be overestimating that number.
For smaller projects, using low-level facilites can get you big gains. Even for larger projects, optimizing the hotspots in code with low-level constructs from within a high-level framework can show great results. In fact, this is how many high-level languages are implemented.
However, while the human mind is the best code optimizer out there, it is also the most frail and inconsistent. While you can make very tight routines in Assembly on a good day, what happens when you are sleep deprived, up against a hard deadline, and stuck trying to figure out why the program keeps crashing? You know, in the real world. Compilers, while maybe not producing the absolute best code for a particular instance are very consistent about producing pretty damned good binary output billions of times 24 hours a day/7 days a week.
Code production time is also a factor. If you are working on a project that's a few tens of thousand lines of code, Assembly language -- with a competant code author behind it -- can show amazing results over the Python version (for example). But the Python version was finished and debugged days or weeks before the Assembly language version was code complete.
Your C++ example is a bit misleading though. Chances are that you were looking at the assembler's output of iostreams. iostreams implementations, while getting better, are not anywhere near the small size of stdio.h or raw assembly output to the console. Then again, iostreams is far more portable and flexible than either of the previous two. It's a tradeoff, just like everything else in the world: convenience/specificity. Take out iostreams and replace it with a home-grown implementation or stdio.h and you'll notice some "tighter" code.
In addition, compilers keep getting better. A good optimizing compiler is nothing to sneeze at nowadays. As a whole, compilers are measurably better than five years ago, and worlds better than they were fifteen years ago. Some of the best human minds are writing general code optimizers out there today.
Add in the final tidbit that assembly isn't portable. If you are targeting a particular embedded platform with strict space requirements -- a small minority of all development projects out there -- C with Assembly fits the bill. As soon as your platform changes because a vendor went under or requirements demand a faster processor or whatever, all of that Assembly is basically useless. You might be able to use some of the same general algorithms, but you're basically talking about a rewrite. Then again, if you wrote some Assembly code that is more generic, it's not heavily optimized is it? It also doesn't work too well if your target includes multiple platforms from the start.
Want to write a portable, network-aware program? If you use C, be sure to eat your Wheaties in the morning, because you're gonna have to spend a while typing in all of the #ifdefs. But you'll just make it clean and compile for Linux, FreeBSD, Solaris right? What if Windows or BeOS are requirements for your project? #ifdef #ifdef #ifdef
OR!
You could write it in Perl, Python, Java, or any of the other "dirty" high level languages and worry about those clock cycles after you've profiled it and seen the need. This of course doesn't remove the need for proper design before you start, but we were talking about implementation.
Remember: Premature optimization is the root of all evil.
- I don't need to go outside, my CRT tan'll do me just fine.
Computers double in complexity every 18 months. Programmer productivity doesn't. The only way to get programmer productivity to keep pace is by augmenting programmer intelligence with computer intelligence.
there has been a steady increase in the level of abstraction they use
So, C is more abstract than LISP? And C# is more abstract than Haskell? Or is this "steady increase" just an artifact of how you choose your examples?
Imagine you are hosting a party. The first person to show up will either be taller than you, or shorter. And (if the people are showing up in a random order) there is a non-zero chance that the next person to show up will either be shorter than both of you, or taller than both of you. As new guests continue to arive, we should expect the height of the tallest person present to go up, and the height of the shortest person present to go down.
I would argue that the same thing is happening with programming languages. We are not just seeing higher and higher level languages, we are also seeing lower and lower level languages (e.g. so-called programable hardware, PLAs, etc.) at the low end. More than anything, we are seeing a steady increase in the number of FORTRANistic languages that dwell somewhere between BASIC and C.
But no grand trend in any particular direction.
-- MarkusQ
This is FUD. There is far more consistency in Java compilers than C compilers in the real world. The difference in output of C compilers is far greater than the difference in output of Java bytecodes. As far as debugging system libraries and the language, while in my years of Java programming I have come across bugs in the JVM implementation and even once(!!!) I came across a compiler bug. I was able to work around the problem in all cases. This is impressive considering that I have done Java development on OS/2, Windows, Linux, and Solaris. I have had far more headaches from C compilers (yes, I code in C as well -- learned it years before Java came along) when bouncing from platform to platform than I have ever had from Java bugs. Come to think of it, I've even come up against a C library bug or two. To suggest that higher level languages are somehow tainted in this respect and C or Assembly is the cleaner answer is laughable.
I misspoke. My intention was to say that iostreams are more portable than an Assembly solution and is more flexible than either. You are correct.
Why? Why does Java need a hack that -- irrespective of the language syntax -- makes drop in text segments like a preprocessor? Do you need it for plugging in a native implementation when available and a Java one when not? Or conditional compilation of some functionality? Java doesn't need a preprocessor for that. You can do it in Java and still take advantage of the Java syntax validators -- which is crippled by the use of a prepreprocessor. It isn't about macros being confusing (which would be a #define and not an #ifdef); It's about them being unnecessary and in this case harmful. Since we're talking about macros and Java, what good would they do? If I write a sufficiently small or simple method/function, the Java compiler will inline it for me. For that matter, so will the optimizing C compiler. #define is of limited use today.
And C is one of the only languages left that DOESN'T have a standard, portable networking API. Java, Python and Perl have all had one for years. Java recently gained a secondary API for non-blocking I/O that takes care of a lot of the speed issues plaguing network apps in the past. And it's supported by all current JVMs. And any Java programmer can look up how to use it in any recent Java tutorial or quick reference.
You want a library? So do I. Those libraries are called java.net, java.nio, and IO::Socket. How are they implemented? Probably in C and/or some Assembly. Do I care? Not if the API is stable and the speed fits my minimum requirements for a job (and they almost always do).
Nothing wrong with "testing out an implementation in a higher-level language" eh? What happens when that implementation is plenty fast and/or memory efficient enough to do the job. Why recode in C?
Java is crippled how? By its standard GUI libraries? How can you compare that to C's lack of any standard GUI library? As for the rest of Java, given the non-blocking I/O libraries, how is Java substantially slower? By all means, give me an example larger and more complex than "Hello World."
Study after study that I have read has demonstrated that algorithm makes far more difference than language in speed contests. The worst problems I have seen in Java code were when arrogant C programmers tried to code it like C and then whine about how it won't work right for them. I have seen Java code where people have made classes called Get_Channels and made instances of those objects in order to call an instance method instead of just making a static method and calling it off of the class definition. This is why Java gets a reputation of slow in the last few years, not because of some inherent limitation of the platform. As far as naming conventions, thank god there's a standard. Any Java programmer who follows the naming standard is pretty well covered that some other programmer will recognize and understand his constructs with a minumum of time and effort. It isn't until C programmers come in with their need to call classes get_channels that things go haywire.
Scope problems? What scope problems? Can you be more specific because I'm not even sure what you have a problem with here.
Yes, and people never bang their head against C or Assembly. I have had many more problems with the limitations of C in the past seven years than I have ever had with Java or Perl. Sometimes C fits the bill. When speed is truly an issue and every little cycle counts, sometimes C (or more often C++) comes to my rescue. For almost every other problem imaginable, C is my problem, not my solution.
Java and C++ have very well defined behavior for object instantiation. For someone who complains that Java programmers just aren't smart enough to handle preprocessor macros, you seem awfully dismissive of your own ability to see what happens in a higher level language when I and others barely even blink.
- I don't need to go outside, my CRT tan'll do me just fine.
Early languages were all very low-level, but successive generations have become higher and higher.
The first language was FORTRAN. The second was LISP. Your premise is fundementally flawed -- languages have not been getting higher and higher level. And before I get any spelling flames, I should point out that back in 1959, the names of both languages were still capitalized like that.
What has been happening is that generic support for useful abstractions has been slowly creeping into our languages. It seems that about once every 10 or 15 years the limitations of the current languages to express those abstractions becomes severe enough that people are willing to make a jump to the next generation of languages.
During the 70's and early 80's, ALGOL-like languages, like Pascal, C, and FORTRAN 77 predominated. From the mid 80's through the late 90's, C++ apparently reigned supreme. Now, in the late 90's and early part of the 00's, we're seeing Java and C# move into the forefront of the developer's mind.
I am loath to call any of those languages high level. C++ added generic support for several OO ideas. Java and C# have added garbage collection and much better support for runtime linking.
But in the end, all of these languages are still fairly low level. The biggest thing that has changed is the overwhelming size of the languages standard libraries, and each Operating System's runtime libraries. We've learned a lot about what programmers need to do in the last 50 years, and we've encapsulated a lot of that knowledge into standard, reusable libraries. In return, those libraries have grown huge.
Think about the size of the libraries available to us -- the KDE libraries, the Win32 runtime library, the suite of standard ActiveX controls available on Windows, the huge Java standard library, CPAN, or the new DOT.NET framework. These are where the advances have been made in the last 50 years, but with a price.
In the 70's, one programmer working a few months could have implemented an entire robust optomized copy of the C library himself, down to the syscall level. A good programmer could intimately understand the entire library in a matter of weeks. Today, it would take dozens of programmers working years to implement the Java or DOT.NET libraries. There is probably no-one who can honestly claim an intimate understanding of any of them. We've reached a point where the standard library is bigger than any one person can understand. At that is probably the biggest thing that is going to impede the development of more complex, useful libraries in the near future...
Slashdot is jumping the shark. I'm just driving the boat.
OK, wild speculation follows...
I think we're going to see languages move in two directions: higher and wider.
The "higher" languages will be designed for bigger abstractions. A lot of these will compile down to today's high- and medium-level languages. Most will be domain-specific. We've always had examples of this (e.g. parser compilers such as yacc, ASN.1 compilers and so on; there are plenty of these for high-level languages like Haskell, such as happy and Strafunski too), but I think we'll see more as we go on.
The "wider" languages will be designed to support higher-level abstractions directly by providing the basic building blocks to the library writer. We see this in C++ template libraries, Haskell combinator libraries, Lisp macro libraries and so on. They will not be as good as "high" languages in their specific domains, but they will be generic enough for "normal" applications, plus they will have the benefit of not requiring a whole other compi.
sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
Early languages were all very low-level, but
:directory '(:absolute "etc" "monkey")
:name "settings"
:type "conf"))
:if-does-not-exist :create
:if-exists :rename
:direction :output)
// is static initialization order even guaranteed?
// get the old one out of the way
// file descriptors aren't garbage collected
// now put back the old file
successive generations have become higher and
higher.
I don't buy this. Explain why Common Lisp lets
me do this, for example:
(defparameter *settings-file-location*
(make-pathname
(defun save-settings ()
(with-open-file (settings-file *settings-file-location*
(prin1 *settings*))
Java, 18 years later, requires code like the
following to approach the functionality of the
previous snippet:
/* We have to shove EVERYTHING into a class.
A singly-inherited one, no less. */
class Settings {
static File settingsFile = new File("etc" + File.separator +
"monkey" + File.separator +
"settings.conf");
static File settingsFileBackup = new File(settingsFile.getName() +
".bak");
public void saveSettings() {
boolean backedUpSettings = false;
if (settingsFile.exists()) {
settingsFile.renameTo(settingsFileBackup);
backedUpSettings = true;
}
FileOutputStream fow;
BufferedOutputStream bow;
try {
fow = new FileOutputStream(settingsFile);
bow = new BufferedOutputStream(fow);
dumpSettings(bow);
close(bow);
} catch (Exception e) {
if (bow && fow.getFD().valid()) {
close(bow);
}
if (backedUpSettings) {
settingsFileBackup.renameTo(settingsFile);
}
}
}
}
Note that this Java code loses on systems like
Mac OS, which store the file type somewhere
besides the filename.
Or how about Common Lisp's condition system,
which allows execution to actually continue
where it left off once an error is corrected?
What about MAPCAR, or DO and DO*? Heck, what
about first-class function objects?
Of course, try getting a job using Common Lisp,
or any other decently abstracted general-purpose
programming language today...
BTW, Slashdot inserted the spurious semicolons
in this post, not me.
TO BUY A NEW CAR WOULD MAKE YOU SEXUALLY ATTRACTIVE.