One of those “kludgy-let's-throw-this-together-in-a-d
As usual, mixed feelings about his work. I shall avoid saying “he was an old annoying fart,” even if he was.
I've read most of his books, but not anything written in the last 5-10 years. I liked his early stuff, but it started getting too cynical and weirdly bizarre to be enjoyable. Player Piano is my favorite; Breakfast of Champions is another good one. Bluebeard is my least favorite of the ones I suffered to read to the end.
Though my real favorite is anything written by Kilgore Trout, even though the books don't really exist. I'd rate Trout much higher than Vonnegut.
Scala's a fairly new language from EPFL (Martin Odersky) which is (according to the developers):
It has some very nice features not mentioned here: Actors and implicit typing checked at compilation time, for starters. Unfortunately, it's turned out to be a real disappointment. In particular it makes absolutely clear why infix languages are a poor choice for functional programming in general.
Let's take a close look at the first FP example in the Scala By Example document, an implementation of Quicksort:
def sort(xs: Array[int]): Array[int] =
if (xs.length <= 1) xs
else
val pivot = xs(xs.length / 2)
Array.concat(
sort(xs filter (pivot >)),
xs filter (pivot ==),
sort(xs filter (pivot <)))
}
The first thing I noticed is the def keyword. You know you're in trouble when the designer can't spare two vowels and a consonant. Lightweight syntax, indeed! Why not just use the letter d and get it over with? Oh, and just look at all those wasted letters in Array, length, sort, etc. Terrible amounts of verbosity linger within this code, marring its inner beauty. Bah! It really should be written like this instead:
d srt(xs: a[i]):a[i]←
(xs.l≤1)?xs:{
c p←xs(xs.l/2)
a.ct(srt(xs f (p >)),xs f (p =),srt(xs f (p <)))
}
Much better! Saves a ton of typing that way, and y'all know we've got a global shortage on 0 bits. (Better recycle while you still can.) Note the clever usage of ≤ and ←. But we definitely need to get rid of those braces, they give the casual reader too much of a hint there's something resembling code in there. Probably should use parenthesis instead, or maybe ` and '.
I implicitly assumed Array[int] was declaring an array of objects with an int index. But of course not; the object type held by the array is in brackets. Why, I don't know. I find this thoroughly confusing, especially as I've used languages where Array[int] really did declare an array with int index; Array[String] was an associative array, etc
Arrays accesses are done with parenthesis: sensibly consistent with the rest of the language, very odd considering the way arrays are declared. I suppose the bracket syntax is some sort of parameterized type thing... but confusing no matter how you slice it.
Worse, there are vals and vars. A val is a constant, a var is a variable. Yuck! One letter differences do not make for happy programmers. But, hey, lightweight syntax, right? Can't spare too many letters, it wouldn't be lightweight anymore.
The coup de grâce is that funky filter thang. You may be wondering what that is; I certainly was. It's an anonymous function! Of course, you moronic dolt! How silly of me! I thought that was a typo... no, see, there's this magic implicit argument being passed into the magic anonymous function.
Scala's only saving grace here is its use of lexical scoping, probably a consequence of using the JVM. (I feel certain many of its users are unhappy about this.)
Scala's type signatures are ugly. The declaration for filter is
def filter(p: t=> boolean): Array[t]
Obviously (and if it's not obvious you're an utter fool who should go back to programming in BASIC) that means p is a function which takes something of type t and returns a boolean. Of course it does! Of course!
I've noticed that keywords are consistently bolded throughout the documentation. If they'd made them just a wee bit longer, just a tad, a smidgen, wouldn't need to be much, say, spare a couple of vowels and a consonant here and there... that crap wouldn't be necessary.
It amazes me people still believe fewer characters = more goodness. I'm not saying we need COBOL-like verbosity. I know for a fact there's a happy medium between COBOL's meandering verbosity and Perl's impenetrable line-noise.
To top it all off, this “elegant” implementation of Quicksort is impressively inefficient. filter has to scan the array three times to build three separate arrays; I hate to ask how those arrays are being allocated.
Only a complete doltish moron incapable of understanding why you put variable types after the variable and array types inside brackets could write a sort routine which took advantage of Quicksort's efficient logarithmic characteristics and only scans the entire list once per iteration.
(defun quicksort (list)
(if (null (cdr list)) list
(let ((pivot (car list)))
(loop
for i in list
when (< i pivot) collect i into ll
when (= i pivot) collect i into ml
when (> i pivot) collect i into rl
finally (return (append (quicksort ll) ml (quicksort rl)))))))
This isn't exactly in a “functional programming” style, though it could be; but it's difficult to efficiently accumulate the results into multiple lists that way. collect is efficient in most CLs and we can assume it has O(1) behavior. It also uses the first element of the list as the pivot point rather than the middle one. This can be changed, but it would require taking the list's length—potentially an expensive operation.
Here's an INTERLISP version. (CL's loop construct used in the previous example was based on INTERLISP's CLISP. This next version uses explicit LAMBDA statements instead.)
(DEFINEQ (ACCUM-MULTIPLE (NLAMBDA ARGS (PROG ((LST (EVAL (CAR ARGS))) (REST (CDR ARGS))) LOOP (COND ((NULL LST) (RETURN NIL)) [T (PROG ((VAL LST)) (SETQ LST (CDR LST)) (RPLACD VAL NIL) (MAPC REST '(NLAMBDA (E) (COND ((OR (NULL (CDR E)) (APPLY (CADR E) VAL)) (SET (CAR E) (LCONC (EVAL (CAR E)) VAL)) (GO LOOP]) (GO LOOP)))) (QUICKSORT (LAMBDA (LST) (COND ((NULL (CDR LST)) LST) (T (PROG (LL ML RL (PIVOT (CAR LST))) (ACCUM-MULTIPLE LST (LL (LAMBDA (VAL) (ILESSP VAL PIVOT))) (ML (LAMBDA (VAL) (EQUAL VAL PIVOT))) (RL)) (RETURN (APPEND (QUICKSORT (CAR LL)) (CAR ML) (QUICKSORT (CAR RL]
The INTERLISP code probably looks a little strange. LCONC isn't in CL at all (tail concatenate list—an efficient way to append a list to the end of a list) and RETURN is needed to return values from PROG. Its one advantage is that it doesn't use any auxiliary storage for lists, the list is sorted in-place.
Most any LISP (or Lisp) implementation, no matter how primitive, can express the necessary algorithms in an efficient manner without resorting to macros, builtins, or syntax redefinition. You could obviously do it in Scala as well, but it would lose all its... charm.
I suppose the INTERLISP version isn't “functional programming” either because it has side-effects (the horror!) but it's functionally very similar to the Scala version—only it's a lot easier to read. No mystery implicit arguments, no funny array syntax garbage. And almost any programmer could puzzle out the Common Lisp version.
At this point I'm wondering what the real point of Scala is. Writing mostly-functional routines without resorting to Java's anonymous class syntax is helpful on occasion; Actors instead of explicit threads is Good; safe implicit typing can be a powerful, flexible tool more easily understood than parameterized types. Unfortunately, by making the syntax “lightweight” they've thrown out the baby with the bath water.
The general rule for “functional programming” is that the code has to look as terse and incomprehensible as possible. If it looks too verbose, it starts to resemble something normal humans might understand (and want to use). Can't have that.
only supports prenex polymorphism
Prenex. Yes, indeedy, it's... prenex. Prenex! It makes your teeth turn green! Prenex! It tastes like Listerine! Prenex! Oh yes it's Prenex! So have some Prenex, and Whoops.
The word “prenex” appears in the Oxford English Dictionary:
Designating a quantifier placed at the beginning of a formula which affects the whole formula; relating to or involving such a quantifier.
It should be taken out and shot. Or at least roughed up a little, told to go home and to never show its face again. Gaaaah! Where do these space aliens come from, and where can I get some repellent?
Anyway, I'm mighty proud to have brought this phrase to your attention, especially as it only occurs 68 times in Google and you probably could've gone your entire life without ever reading it. I mean, those damn inadequate languages only supporting prenex polymorphism! How dare they?!
Thanks for listening, and have a nice day.
A couple of years ago someone apparently... typed in by hand... the LISP 1.5 assembly sources for the 7090. Why, I can't imagine.
Oddly enough, some other equally batshit person went back through and checked for typos. But it seems nobody's really been able to get it to go.
To make a long story short I've figured out how to use it... but it took some head-banging and a lot of staring. If for some bizarre reason you want to run it (it runs on the SIMH IBM 7090 emulator) the instructions are here.
The most frustrating part was SIMH's lack of useful error messages, actually. It usually says something like "didn't work", only with a lot more words.
I've been thinking about this a lot lately, because I've been advising on projects done by others. In almost every case I've recommended complete rewrites rather than trying to modify the existing code, and with some pretty solid justifications. (Granted, a lot of it is crappy FORTRAN which has to be rewritten in C, some of it is all-too-typical curdled LISP, and some of it is just very old spaghetti code written under a tight deadline.)
Much of it was written with care and consideration for re-use, with copious documentation. Some of it was written as libraries intended to be used as "building blocks" for other projects. Yet it's becoming all too apparent that one programmer's good, tight, clever design is another programmer's jumbled mess.
As a specific example, much of the LISP code tries very hard to be efficient and clever, lots of macros, memoization, and tossing about of lambda expressions. It is, in fact, quite clever. It was not prematurely optimized, but it was optimized to run on hardware circa 1995 out of sheer necessity.
Unfortunately, it is too clever for its own good. It is near-impossible to maintain... unless you were either the person who wrote it, or are willing to spend a year (and to understand the whole system it literally could be a year) understanding the particular set of clevernesses involved. It could be rewritten using a brute-force approach in two or three months. You figure it out.
The tradeoffs have changed dramatically. If I were to write the equivalent functionality of that LISP code today (I'm not actually writing any code for this project, I'm just giving advice) I wouldn't worry about making it efficient; I can just toss more CPU and RAM at it, and a programmer's time is more valuable than a high-end computer. The problem can be easily parallelized if needed, but I really don't think it will be necesssary.
Overall it's been a disappointing experience. One lesson I've learned is that it's very hard to write truly reusable code beyond the level of strcmp() or regmatch(). Granted, a lot of this code over 25 years old, but some of it isn't; some of it is C++ which, again, tries to be too clever with template abuse and other all-too-typical “I'm a good C++ programmer!” tricks. The people who write this stuff should be publically ridiculed.
I should probably become a design consultant, because I can avoid flogging my particular design methodology; I don't believe in design methodologies.
Oh, this reminds me of a situation I ran into a few years ago. There's a GCC extension which allows the middle value of ?: to be omitted; x?:y returns x if it is non-zero, otherwise y. I'd never seen it actually used. In the process of porting someone else's code to a new OS I ran into it (GCC warning) and promptly eradicated it. The code in question pretty much had to use GCC (it had lots of __asm__ statements) so it wasn't an unsafe thing to use; but I removed it because it was unnecessarily non-portable and very rarely used language feature. I ended up in a 15-minute shouting match with the person who wrote the code, of course; the other programmer claimed I was an idiot if I hadn't seen that extension before, etc, etc. Reminds me of the same people who won't use "unnecessary" parenthesis when it would be helpful for clarification.
It's a helpful word; we can be pretty sure any writing which seriously uses it is not worth reading.
I just tried to access this Google search:
http://www.google.com/search?hl=en&ie=UTF-8&oe=UTF-8&q=site:mail-archive.com+gcl+2.7.0
I am politely informed that my query “looks similar to automated requests from a computer virus or spyware application”.
Sure, sure, whatever you say. But if I remove the "hl=en" part, it goes through OK. Crazy idiots. Sounds to me like the Google Guice is destroying their braincells.
I dearly wish they had a way to be contacted. (They don't; they don't want to hear from the unwashed masses. Plus they'd be swamped with requests from pathetically desperate SEO morons.)
For whatever reason, today I ended up reading Wikipedia articles on the new binary prefixes. The discussions in their talk pages is a little silly.
As I'm sure a few of you are aware, the IEC declared a few years ago (apparently without discussing the issue with any programmers) that 1K of memory is equal to 1,000 bytes of memory because K = 1000 in the metric system. 1M = 1,000,000 bytes. Etc. The decision has been rubber-stamped by various organizations including ANSI and IEEE.
While there's a certain symmetrical logic to this, it's equally obvious that few computer programmers need memory units in powers of 10. I personally can't think of a single time when I've needed a 1KB = 1000 bytes unit. I use 1KB = 1024 bytes all the time, probably every day.
There rarely is any confusion in programming. I've run into the issue exactly once, and that was because some of us took a 160MB disk drive marketing figure too literally. Supposedly some poor consumers are confused because the poor waifs don't know if a 500GB disk drive = 500×1000×1000×1000 bytes drive—or is it 500×1024×1024×1024 bytes?
It's neither, of course. But let's take a slightly different tack.
1K = 1024 bytes has been around since at least 1959. The original 4K Drum FORTRAN ran on an IBM 709 with 4,096 words of memory. The abbreviation was used both in their source code and the marketing literature. IBM used 4K and 8K to describe 709s with various memory configurations. (I'm sure there are earlier references; that was just the earliest I'm aware of.)
DEC used 1K as 1024 bytes very consistently. The earliest DEC reference I've found was 1968, in a manual for a DF32 disk drive with a total capacity of 131,072 13-bit (yes, 13-bit!) words. At the start of the manual they spell out all the numbers, but throughout the rest of the manual they consistently use K = 1024. The source code for the driver uses K = 1024 in the comments.
In general, DEC usually referred to a PDP-8 with 4,096 words of memory as a 4K device and 8,192 words as an 8K device; I would not be surprised to see this in the earliest marketing literature for the machine. PDP-11s were always described as having nKW of memory.
I'm pretty sure I've seen the K designation used for 1K and 2K memory devices in data sheets written in the mid-1970s, but I don't have any references to hand. Certainly by 1980 the use of K = 1024 was very consistent in datasheets.
Here's the point of all this: K = 1024 is all over the place. It certainly didn't start in the microcomputer era; it probably started with IBM back in the 50s, though it would not surprise me to find even earlier references. It's a convenient unit to use, because nobody in their right mind wants to say things like a 4.096KW machine. Might as well just spell it out.
Just a look at various popular uses which are at variance with the K = 1024 standard.
One argument I've read was that the earliest users just rounded off the number instead of assigning K = 1024. This is entirely plausible, but a bit irrelevant.
I have definitely seen computers with 64K of memory referred to as 65K computers. And a Google search for 65K turns up a reference to “65K colors” as the very first hit.
But the 65K number is wrong! It should be 66K, not 65K; 65.536K rounds up to 66K. I can't give these uses much credence. Those using 65K are truncating because it's an easy way to remember it's just about 65,000. (I've memorized 216 = 65536, and so don't need the approximation.) It's typical to say things like “65,000 colors,” but 66,000 is closer to the actual value. 65,000 has a safety factor, so it may be preferred by some users.
One of the most common variants on the K = 1024 theme is the 3.5″ floppy. Their capacity is
80 tracks × 18 sectors × 512 bytes/sector × 2 sides = 1474560 bytes
[did that from memory, ha!]
which translates to 1,440K or 1.40625MB. They are typically referred to as a 1.44MB floppy. 1.44 is a nice round number which sounds good and is easy to say/write/remember. Whoever picked that term chose units which made it come out that way; in their universe, 1MB = 1000 * 1024.
Of course thanks to filesystem overhead I usually can't store 1,440K worth of information on a floppy. (Though if I don't use a filesystem and don't hit any bad sectors, I can store exactly 1440K worth of data.)
10baseT Ethernet is usually described as a 10Mbps protocol instead of a 9.54Mbps protocol. The hardware transmission layer uses Manchester coding at a 10Mhz clock rate, so it is theoretically possible to transmit 10 million bits of information per second.
Now there's no way you can transmit 10 million bits at the data transmission layer because of framing overhead and collisions. So while there's some basis in calling it a “10Mbps protocol,” in reality it's nowhere near that efficient.
10Mbps is used because it's a nice round number which is easy to remember. If I need to know how much data I can transmit through Ethernet, while I can assume it's somewhere in the 8-9Mbps ballpark, I can't use that 10Mbps number literally. It's just a marketing number.
I don't know what the typical capacity number is, but I seem to recall it's in the 600-650MB ballpark. But those are just approximations. The exact capacity depends on several factors related to how it is recorded. The underlying units matter little whether it's K = 1024 or K = 1000, you can't use those values to determine exactly how much a CD-ROM will hold.
Single-layer DVDs are often described as holding somewhere around 4-4.5GB of data, and dual-layer 8GB. Again, mere approximations.
Typically we see hard drives described as “500GB”. Drive manufacturers assume the smallest possible units, or K = 1000; it's clearly to their benefit to make the capacity numbers as large as possible.
If you advertise your drive as holding 465GB and everyone else uses 500GB, you'll lose sales. If you add enough extra capacity to your drive so that you can say it's a 500GB drive using 1024-byte units, you're losing a golden marketing opportunity because you could list it as a 550GB drive instead.
It doesn't really matter because no disk drive in the world has an exact capacity of 500,000,000,000 bytes, and no one with any sense is going to base their storage capacity calculations on that 500GB value.
It's typical to see DSL advertised as 768Kb or 4.2Mb or something. Ditto with cable.
Those numbers are just as approximate as the Ethernet and disk drive capacities I listed above. They're marketing speak, not actual figures which can be counted on to mean anything (other than you'll get values probably within an order of magnitude of the ones listed).
None of the marketing uses present good arguments for assuming K = 1000. What marketers do has little relevance for people who really make use of the units involved. Consumers shouldn't be basing capacity or bandwidth calculations on numbers like 500GB or 512Kbps; not only is it an approximation no matter what units you use but overhead has to be taken into account.
Of course this argument is futile, because the IEC has spoken. Still, the new units have yet to catch on, and it's going to be a very long time before they do—if ever. If there were an actual need I would see them catching on very quickly, but so far I just don't see a use for 1KB = 1000 bytes.
Google Guice. Some of the hilarious highlights:
Annotations finally free you from error-prone, refactoring-adverse string identifiers What a relief! I was having such horrible error problems with my refactoring-adverse string identifiers. And just the other day I was talking with one of the neighbors and she was mentioning all the problems she was having refactoring her string identifiers. Now I can tell her about Guice! Hurrah! We are saved! The rapture is upon us!
empowers dependency injection. You know, my life has been magnificently incomplete without the power to inject dependency directly into my veins. Thank you, Mr. Google! I am empowered now!
cures tight coupling Hmmmph. So does sexual lubricant, but I don't think I want to use it on my dog.
This has got to be a joke. Either that, or it explains why Google got rid of the "More results from" links: their minds are dead.
I know there are several which are very well known, but this one doesn't seem to be. I ran across this aeons ago when I was rebuilding HPsUX (die, bletcherous cancer!); it was still in their sources! I very distinctly remember printing it out and hanging it on the wall.
This is from the SysIII (y'all remember System III, don't'cha?) chroot.c. I'm fairly sure it was also in V7 and possibly earlier, and it was in at least one SVR3 release:
# include <stdio.h>
/* chroot */
main(argc, argv)
char **argv;
{
if(argc < 3) {
printf("usage: chroot rootdir command arg ...\n");
exit(1);
}
argv[argc] = 0;
if(argv[argc-1] == (char *) -1) /* don't ask why */
argv[argc-1] = (char *) -2;
[...]
Uh, OK. I won't.
Actually, gosh darn it, I will. Why the fra is it looking for -1 and changing it to -2?
The answer is in the last code extract I posted in the comments. I won't answer the question so's to spoil the fun, but the answer's right in front of you. Hints: Where did those pointers come from? How could the next-to-last pointer end up being -1? How can we be sure setting it to -2 will give the equivalent string?
Anyone else notice that Google isn't returning the “More results from” link for most sites? No explanation, either.
If you haven't noticed, do a search for "intel x86" without the quotes. One of the top hits is Intel's website, but it only lists two pages... not likely. If you happen to get the right set of servers you'll get the “More results from” link, but for most of them I don't see it.
I'm now writing an Opera UserJS hack to make it show up. I really have come to rely on that particular feature...
Hopefully the idea isn't patented.
You may have already seen this Java applet. If you haven't, go check it out. You'll like it.
(On my Linux laptop I can't type into it. I don't know why, and don't want to take the time to figure out what's wrong. That's exactly what I mean about Java applets being inherently broken.)
The idea I think is “brilliant” is the clock, which cleanly and easily solves a problem I've been puzzling over for a while. (Try using the applet for a bit, then moving the hands.)
I saw this article and decided to comment on it, though there have been several similar essays as of late.
Most of the articles on the subject are a little misguided, and fail to take into consideration the actual technologies involved. I'd also claim most opinions about this are, at best, subjective.
Java applets have pretty much failed because the implementation was and is lousy. The plugin mechanism used to integrate them with the browser wasn't up to the task; they were both too loosely coupled (in the modern DOM sense) and yet too tightly coupled (depending on a browser to provide near-hardware-level interfaces is wrong).
The reasons for this made sense at the time. It was from Sun, and running on Unix was important; back then there were many actively-used Unix platforms and Sun wanted a way to support them all. That doesn't change the fact that it just hasn't worked, and will never offer Windows users the level of performance they've come to expect from more closely coupled technologies.
It also didn't help that AWT widgets looked bad no matter how you sliced it. That's something Sun could and should have addressed from the start. Bold, black, non-anti-aliased Lucida on a dingy gray background is hideous—unless, it seems, you're a GUI designer at Sun. Combine that with dicey event dispatch and you've got a real mess.
(Lucida is a hideous font. Period. There. I've said it, and I'm proud.)
JavaScript works well because it's integrated at precisely the right level for the job it's trying to do. Writing dynamic web pages is a much easier and more rational task for the browser to handle. There was also a serious commitment from the major browser vendors to make DHTML work. If Java applets had initially used a similar mechanism they would've fared much, much better; it's not likely we'd be having this debate today.
JavaScript's obvious problems are its lack of speed and an overall poor language design. Writing and maintaining large JS projects with multiple programmers is a challenge, to say the least.
Sometimes performance isn't an issue, and this problem will soon be addressed with better JS interpreters and JS compilers. But the lack of warnings and little in the way of type or argument checking are a more serious problem. Experience has shown most pseudo-free-form languages don't do well in the longterm.
(I've written hundreds of thousands of lines of code in various scripting languages over the last 25 years. So I think I know a little bit about it.)
There's also no clean way for JavaScript applets to have network access, though AJAX addresses this to some extent. But along with AJAX come lurking security issues, ones which haven't been considered. People are going to be pissed when that seemingly-innocent multiplayer game turns out to have a builtin SMTP proxy.
I feel people make too much of the cross-browser JS issues. For at least the last year it's been reasonably straightforward to write substantial Javascript apps which run cleanly on the big three browsers. (That was my experience; feel free to disagree.) There are usually more problems related to CSS than JavaScript or DOM.
Flash works well because it's completely unintegrated with both the web browser and the operating system GUI libraries. There's a small amount of linkage between the browser and a Flash applet, but it's little more than saying “put your window here”. They rarely try to present UIs which resemble desktop apps. (If they do resemble a desktop GUI it's just a happy happenstance of the particular Flash design process.)
Traditionally, Flash-based apps have had poor UIs. They're usually designed by “GUI designers” or graphic artists, and the results speak for themselves.
There have been a number of complaints recently about JWS. I feel strongly most of the problems are related to applet authors making bad design decisions, and not with any inherent problem with JWS.
It seems 90% of JWS applets use bogus certificates or self-signed apps, when most of the time they don't need to be signed at all. JNLP provides safe mechanisms for unsigned applets to do many common operations (the usual issues are filesystem or network access, both of which can be done without signing).
(Oh, and my beef about the Preferences class requiring a signed app is going to be fixed in the next release. Though it turns out using other mechanisms for preferences storage has advantages.)
As for which user interface is “best”... like so many things, it depends. I wouldn't use standard GUI widgets to implement a pool game, and I wouldn't use a GUI based on a pool game for filling out forms. But I would use standard GUI widgets for the pool game's preferences dialog, and if the form filler needs something more than standard GUI widgets I'll use it.
I also completely disagree that the majority of users insist these applications be embedded within the web browser window. Sometimes that's useful, but sometimes it isn't, and sometimes it's irrelevant. Some AJAX developers also disagree; quoting from the AJAX page on Wikipedia:
The most obvious reason for using Ajax is an improvement to the user experience. Pages using Ajax behave more like a standalone application than a typical web page.
Bring up a separate window with no browser menu and the user could easily forget (or never even realize) the app is really DHTML running in a web browser. And they aren't going to care one way or the other.
Each of the technologies under discussion has limitations. I iniitally wanted to use Javascript for some of the applications I've been writing lately, for many of the reasons listed by the article I mentioned at the start. JS is built into the browser, no downloads, it just works. But it's far too slow for my needs, and I found the lack of solid debugging facilities and code checking painful.
Flash+Flex+blahblahblah was one alternative I briefly considerered, I didn't like the lack of integration with the desktop, it has most of the same problems as Javascript, development tools are usually Windows-only, and it's hard to design a professional-looking interface with Flash instead of a cutesy toy.
Swing + JNLP + JWS has worked well for me. I have many sincere issues with Swing, and Java in general, but most of them have workarounds. (I was especially happy after I hacked a command-line editor into jdb.)
Ajax is popular because we know that the necessary software for the client side is already installed.
My experience has been that most users also have a JVM installed, and usually something newer than 1.4.
From a personal perspective I absolutely hate installing software in general. I have yet to run into any significant issues with downloading and updating JWS or Java, and I'm running on three vastly-different platforms.
(Cue someone complaining about how their computer caught on fire and exploded when they tried to install JWS. Yes, yes, I know. That doesn't mean it doesn't work for most users. I've honestly had more problems with Flash upgrades than I ever have with Sun's Java or JWS.)
I wouldn't try to distribute an app based on, say, Python or TCL/Tk these days. Few users have the software and hardly anyone would be willing to take the time to install it. But Java is a different matter entirely: it's installed because it's ubiquitous, and it's ubiquitous because it's installed.
[referring to the Aerith demos]
If you click on the JNLP version link on that page, it will appear to start up, downloading a bunch of stuff and asking you questions.
It asked me one question: did I want to run this app signed with a bogus certificate? And I quite sensibly said, “Heck no!”. But that goes back to what I wrote above: there's no reason that app needs to be signed if it's just a demo. Complain to the people who signed it with a bogus certificate, not Sun.
[It turns out there's a bug where, if the app needs Java 1.6 (needed by the Aerith demos), JWS ignores this requirement and lets the user run it even if they only have 1.5. Very few real apps should absolutely require a 1.6 JVM.]
It's not impossible to build GUI applications with Java, but it's been 10 years and there are still installation hiccups with applets, Java WebStart, and regular applications.
I agree it's not impossible to build GUI applications with Java:
(Click to see a larger version; I will have a demo version available
soon.)
It's amazing what a nice color scheme, good choice of fonts, and clean 3D effects can do for a Swing-based app.
I completely disagree that JWS is inhrently flawed and cannot present a good user experience. I've had virtually no complaints about installation problems with the apps I've distributed via JWS. That's not to say it won't happen, but it's clearly not going to be for me the huge issue some are making it out to be.
It's true it's not quite the brain-dead-simple experience one gets from running a Flash-based app. But keep in mind Flash-based apps have until recently been very limited in functionality. Now that Flash is offering similar capabilities, they'll also need to start asking the user exactly the same sorts of questions JWS currently does.
As always, use the technology which makes the most sense. Each of the three listed here have tradeoffs: development time versus UI versus speed versus desktop integration versus browser functionality. No one of them is or ever will be clearly superior to the others; be glad we have such a diverse set of options.
Developers tend to see the world in terms of the particular thing they're working on, be it game software or database access. Don't forget that not everyone does the same thing you're doing.
I'm not contemplating suicide... but I truly, sincerely believe there's a serious problem when searching for "suicide" turns up the Wikipedia article on the subject as the #1 entry.
And if you can't understand why that might be a problem, try reading the #2 entry.
javac is a darn clever piece of work. A lot of tweaking later:
$ cat Test.java
#pragma javac_stop_being_annoying
public class Test {
public static void main (String[] args)
{
String s = "abc";
if (s == s)
{
System.out.println ("Hi!");
}
if (s eq s)
{
System.out.println ("Hi!");
}
if (s eq 0)
{
System.out.println ("Hi!");
}
if (1 eq 2)
{
System.out.println ("Hi!");
}
}
}
$ javac Test.java
Test.java:7: operator == cannot be applied to java.lang.String,java.lang.String
if (s == s)
^
Test.java:15: incomparable types: java.lang.String and int
if (s eq 0)
Brilliant! Genius! [Note line 19 compiles (autoboxing, yay!) but the test will fail. I should probably fix that. Easiest is to just disable autoturding entirely; perhaps javac_stop_beiing_annoying should do that as well.]
Source files without the javac_stop_being_annoying pragma are compiled as before, including allowing eq and neq as identifiers. (I love the #pragma bit, but I'm easily amused.)
Heh. This actually prints "Hi!":
if (1 eq 1)
{
System.out.println ("Hi!");
}
This doesn't:
if (129 eq 129)
{
System.out.println ("Hi!");
}
Oh, those magnificently wacky Sun guys and their flying machines.
javac_stop_being_annoying also gets rid of the += syntax for String lossage. What were they thinking?
I have a lot of beefs with Java, ones which are only growing over time. But with Java's equivalence operators we reach the pinnacle of poor design. I think the designers should and could fix this before adding eye-candy like closures or properties.
StringBuffer s1 = new StringBuffer ("ha");
StringBuffer s2 = new StringBuffer ("ha");
System.out.println (s1.equals (s2));
prints false because StringBuffer “doesn't implement equals ()” and the default equals () implementation is equivalent to ==.
No good explanation exists as to why this is so; there's a confusing answer along the lines of “the Java API uses equals () inconsistently” but that doesn't explain anything. Sure, if I read the documentation for StringBuffer I'd learn it doesn't implement equals (); my working assumption is that something as basic as a string class would implement it.
The equals () method has a contract: if two objects of the same type are considered to have the same value, it returns true. It is clear the behavior of StringBuffer does not meet this criteria; it is broken.
General rule: surprising behavior is Bad in a programming language. Not implementing equals () according to its contract is surprising behavior. Rather than using some bizarre default behavior, Object.equals () should throw a “not implemented” exception when it is invoked.
Better still, the code shouldn't compile in the first place.
Another gotcha:
void foobar (int a, int b)
{
[...]
if (a == b)
{
blorknitz.add (Integer.valueOf (a));
}
else
{
fleeblui.add (Integer.valueOf (b));
}
[...]
}
Hmmm. It'd be more efficient if I passed in two Integers instead of int because I have them right here, the compiler's just autoboxing and unboxing them. I can fix this easily:
void
foobar (Integer a, Integer b)
{
...
if (a == b)
{
blorknitz.add (a);
}
else
{
fleeblui.add (b);
}
...
}
Say, that's funny. It's not working right anymore. Wonder why? (More scary: it does work consistently for particular values of a and b.)
Given the current state of Java's equivalence operators javac shouldn't allow programmers to write code like that in the first place. More specifically, == needs to be one type of equivalence or the other instead of varying its behavior between built-in types and instances of Object. In BOOP, if you want to know if two handles point to the same object:
if (@address (a) == @address (b))
{
...
}
That's absolutely clear. It takes more keystrokes but that's OK. (And it's not actually doing function invocations, the compiler does the right thing instead.) I'm not going to read that five years from now and wonder what it does; I'll know.
You can't even code defensively against this problem in Java because invoking .equals () on a “non-object” (whatever that is) doesn't work.
What does this do?
String s = new String ("hi");
String t = s;
System.out.println (s == t);
s += " there";
System.out.println (s == t);
t += " there";
System.out.println (s == t);
Clever dog, I bet you thought it'd print true three times, right? Or if you're forgetting how == behaves, perhaps true, false, true. Nope! It prints true, false, false. That definitely seems broken; s and t are handles for the same object, and all I did was append to the String object we created in the first line... right?
Wrong! Java really does this:
String s = new String ("hi");
String t = s;
s = new StringBuffer (s).append (" there").toString ();
This Is Not Good.
Another silliness:
Long l = Long.valueOf (0); System.out.println (l.equals (0));
Prints false. Bwahaahahahaahah! Why? WHY? That makes no sense at all.
It's because Long.equals () is defined something like:
boolean equals (Object o) {
if (o != null)
{
if (o instanceof Long) { return ((Long) o).whatever == this.whatever; }
}
return false;
}
We passed in an int, which is autoboxed to Integer, and the test fails because o is not an instance of Long.
“Makes sense, right?”
Only if you're from Bizarro World. How could they get so many little things wrong, then fail to fix them in a timely fashion?
Some crazy people want Java to be more Lispish, and think closures are the greatest thing since canned Spam. I disagree.
Closures are anonymous code blocks which can be passed around and evaluated. That's OK as far as it goes, but it can lead to very unreadable code.
They aren't very popular. The article mentions that “Scheme, Smaltalk, Ruby, and Scala” provide them; honestly, none of those are languages I'd ever use for anything practical. The people who do use them usually come from Scheme/Lisp backgrounds, and enjoy the “flexibility” these sorts of constructs give. (I call it “curdled milk programming,” it's usually full of ugly lumps.)
The final deciding point for me is the syntax. Here's a prime example:
<K,V,throws X>
void for eachEntry(Map<K,V> map, {K,V=>void throws X} block)
throws X {
for (Map.Entry<K,V> entry : map.entrySet()) {
block.invoke(entry.getKey(), entry.getValue());
}
}
Ugh. If I stared at that for an hour I might be able to guess at what it's trying to do. Not something I'd want to see when debugging someone else's code. And that's a simple example!
There's also this proposal, one which is much more readable and less powerful. I think that one's OK; it doesn't lead to users writing incomprehensible gobbledygook, like the mess C++ templates have become. (In fact, that's exactly what that example above reminded me of.)
Many programmers believe shorter syntax is better. I'm just the opposite, I like lots of syntax. To me, consistency and ease of reading is more important than saving keystrokes. And I think most programmers would agree, otherwise we'd be using languages which resemble APL.
This is just kitten-eatin' badness in Java.
I need efficient access to four integers. The usual method in most languages is to use an array. But...
public class Foobar {
public int b1[] = new int[4];
public int b2[] = new int[4];
}
public class Barbaz {
public int b11, b12, b13, b14;
public int b21, b22, b23, b44;
}
Suppose we need to save b1/b2 temporarily; what's the fastest way?
Yep, you guessed it:
Foobar p1, p2; int h1[] = p1.b1; int h2[] = p2.b2; int p10 = h1[0]; int p11 = h1[1]; int p12 = h1[2]; int p13 = h1[3]; int p20 = h2[0]; int p21 = h2[1]; int p22 = h2[2]; int p23 = h2[3]; // then put them back later
This is at least one order of magnitude faster than using clone/System.arraycopy. But it turns out we'd be even smarter not to use the array at all.
Which is faster?
Foobar f11; java.util.Arrays.equals (f11.b1, f11.b2, 4);
versus
Barbaz b1; b1.b11 == b1.b21 && b1.b12 == b1.b22 && b1.b13 == b1.b23 && b1.b14 == b1.b24
The latter, of course. By a factor of five.
So how about:
Foobar f11; f11.b1[0] == f11.b2[0] && f11.b1[1] == f11.b2[1] && f11.b1[2] == f11.b2[2] && f11.b1[3] == f11.b2[3]
It's slightly slower than the Barbaz version. But if I'm going to write code like that, why use the array in the first place?
Here's a really evil one: which is faster?
public void f (Foobar foo, int x) {
w += foo.b1[x];
}
or
public void f (Barbaz foo, int x) {
w += (x < 2 ? (x == 0 ? foo.b11 : foo.b12) : (x == 2 ? foo.b13 : foo.b14));
}
Almost exactly the same speed. (If anything, the Barbaz version is faster. Unfortunately, adding a member function to Barbaz to simulate the array makes it about 1/2 the speed.)
Why is this? Because arrays are not an integral Java type. Dumb. Very, very dumb. The reason arrays were invented was so I wouldn't have to use typo-prone code like the above. In C we can expect the compiler to optimize much of this, but not so in Java.
The usual Java answer seems to be "use collections". I think this misses the point; they're much slower. Sometimes you really do need efficient code, and arrays can be an extremely efficient data structure.
why signed certificates are so damn expensive these days?
I remember buying one about 6-7 years ago, and it was like $40. Now they cost upwards of $200 per year.
I vaguely care about this because I've been writing JNLP apps, and it seriously would be to everyone's benefit if they were signed. The prohibitive cost just doesn't justify it.
(I'm aware of the various "open certificate" efforts, but they're not feasible—if you seriously look at the requirements, few humans would ever be able to get a code signature. If you're one of the "in" people it's easy; I'm not.)
Ridiculous. I'm almost tempted to start my own.