So, Google has released a new programming language, which turns out to be a slightly cleaned up version of Algol-68. While depressing, this doesn’t come as a large shock to me- if your prime requirement for a new language is that it doesn’t force you to learn something new, as it seems to be for most programmers, than the ability of the industry as a whole to move forward is limited at best. Radical advances require radical changes.
I’ve taken a couple of swings at writing about Go before this, but nothing stuck. Most everything I had to say I’ve said before, and when even I am bored with my ranting, it’s time to change the subject or shut up. But I’m left with one outstanding question I’d like an answer to, before I move to blissfully ignoring Go:
Why is it that still, today, it’s considered an advantage if not a requirement for a new language to be capable of writing an operating system?
I spent ten years doing device drivers, BIOSs, and embedded programming (before switching to a level where there is a computer under all these layers of abstraction somewhere, but I don’t have to touch it :-). And for all the warts C has, it’s not going to get displaced easily. To displace C, you have to be enough better than C that it’s worthwhile rewriting all the C code that’s out there. This can happen- but the easiest way I see it happening is some language combining C and VHDL. Being able to have the same code as both software (for cheap, simple hardware) or hardware (for performance), that would be something that could displace C in the embedded/OS space. I don’t see how a pure software language can be enough better than C to be worthwhile replacing it.
But whether the OS or embedded people actually adopt the Go language seems to be a moot point- the advantage supposedly is that they could. That because the OS could be written in this language, this some how makes it better for us to write our accounting system or web server in it. Why is that?
Back in the bad old days of MS-DOS, there was a good reason for this. As you didn’t have an OS to speak of, most apps of any size and complexity did end up having to contain what were effectively device drivers. And as computers had neither the memory nor the speed to support high levels of abstraction, a tight coupling between the device drivers and the rest of the app was necessary.
But it’s not the eighties anymore, and hasn’t been for decades. And with the introduction of Windows 3.1 seventeen and a half years ago, applications no longer need to, indeed generally no longer can, include their own device drivers and fiddle directly with hardware. Some overhang was to be expected, but by the mid-1990′s, I expected that we had put the era of bit-twiddling behind us. But with Google’s introduction of Go, it appears that I am- or possibly Google is- sadly mistaken about the demise of the low-level languages.
The one reason I can think of, other than sheer abject stupidity, is performance. Or rather, optimization.
Let me back up here a minute. An optimization is a transformation of the code from a (hopefully) simpler and more abstract implementation to a faster one that does the same thing, generally at the cost of simplicity and abstraction. A simple example of this might be replacing a (slow) divide by a power of two with a (fast) shift right. Note that the optimization is just the transformation of the code- I’m not limiting who, or when, the transformation is applied. More specifically, the optimization may be applied either by the compiler, the run time, or the programmer.
The advantage the bit-twiddling languages like C, C++, and now Go, provide is the ability for the programmer to apply optimizations by hand. This is an advantage to a language designer- all that tedious optimization work can be skipped (notice how proud the Go designers are of compile time- optimizations take time for the compiler to apply). Hand applying optimizations is also an advantage in micro-benchmarks (like the Shootout), and in blog posts and similar. Places where the code size is small enough that the cost, especially in terms of lost abstraction, is negligible.
The problem is that we’re not writing blog posts or micro-benchmarks. We’re writing whole systems. Programs of non-trivial complexity, tens or hundreds of lines of code, possibly even millions. Programs too large for us mere mortals to hold in our heads- at this point abstraction and simplicity are not options, not things that are not nice things to have that we can no longer do without. They are absolute necessities. When our programs are at or near the limits of our comprehension is when the extra complexity of hand optimization can be least afforded- large scale systems programming is the last area that should be doing hand optimization.
The other possibility is for the computer to apply the optimization, either at compile time or run time. In both of these cases, the problem is the same: the computer must
- detect that the optimization can be (profitably) applied, is that the code transformation can be made and does result in (generally) more efficient code,
- determine whether the code transformation can be safely applied, that is applying the transformation doesn’t change the defined semantics of the program, and then
- apply the code transformation.
The tricky part is step #2- basically you have two different programs (one before the transform, and one after), and the compiler has to prove that they behave the same (for some more or less loose definition of what “behave the same” means- see “undefined behavior” in C/C++, for example). It is very easy for step #2 to rapidly become equivalent to the halting problem. And often times the optimization can not be applied without changing the semantics of the program- especially in the face of side effects and exceptions.
Which is why this problem often even hits languages that are not very “bit-twiddly” in nature. The performance hit of not applying the optimization is (or at least can be) very large- and if the compiler doesn’t apply it, the programmer generally will (or at least will want to). For example, because the Ocaml compiler doesn’t apply defunctorization, many in the Ocaml community strongly council avoiding functors altogether, in effect defunctorizing code by hand. Or take this rant (about Google’s Closure)- many of his critiques amount to supposed Java programmers not hand applying optimizations to the source code.
The javascript example is especially relevant to this discussion- as it demonstrates no language avoids the need for performance. You can’t just say “this isn’t a language to do serious computation in, so performance (and thus optimization) doesn’t matter.” If any language could make that claim with a straight face, it would have to be javascript. And yet, despite the supposed unimportance of performance in javascript libraries and programs, not applying by hand certain optimizations is considered a gross violation of acceptable style by experienced javascript programmers. No discussion occurred in the link I supplied above about how much faster the code would be if the hand optimizations were made (or, equivalently, how much slower the code was because the optimizations were not applied). No, the author took the position that because the optimizations were not hand applied, the programmers who wrote it were obviously inexperienced in javascript, and wrote ugly (to an experienced javascript programmer) code.
When hand applying optimizations becomes not a matter of performance, but instead one of style, then what was a problem has metastasized. Abstraction and simplicity are sacrificed even when little or no performance is gained from the sacrifice.
This is why I went ga-ga over Haskell’s super-optimization. It gives us a completely different solution to this problem. It demonstrates that even reasonably large-scale optimizations can be applied- if the language is designed for it. Civilization- and software engineering- advances by increasing the number of things we don’t have to worry about. If you’re designing a language for large scale, system programming, this is the tantamount goal. And the designers of such languages shouldn’t be looking at Algol-68, they should be looking at Haskell.
Why Google doesn’t know this, I don’t know.
18 Comments
Your criticism of Go only makes sense if you completely ignore Goroutines — CSP is a hell of an abstraction, especially since its concurrency is targeted at the algorithm, not the implementation. The point is not to make your code run faster (not that there aren’t Sufficiently Smart Compilers) — the point is to make your code as clear and concise as possible.
Go could easily be the language to make CSP popular in industry (Erlang ain’t it), especially if Google’s NaCL takes off with ChromeOS (Go already ships with it as a compiler target).
The rest of the features are just gravy for systems programming: perfect string handling, baked-in module system, pointers without ‘Undefined Behavior’, no Classicism, no Inheritance, first-class Interfaces… The only things missing are parametric polymorphism for user types, and the ability for the user to implement the runtime’s interfaces.
The biggest thing Go nicked from Haskell is type classes – aka interfaces in Go – but in a dynamic sense rather than a static sense. It’s Go’s primary innovative feature.
My own problems with Go lie more in the direction of what it lacks – the oddness over the magic make(), dodgy confusion with arrays and slices, lack of generics and exceptions – but the need for an efficient but far less verbose language than Java seems pretty clear to me. Particularly needed are decent linkage with C, and prescriptive control over record layout. D plays in the same space.
The green threading (CSP) model is also hard to work efficiently in one of the common runtimes, e.g. CLR or JVM.
CSP is not threading, there’s a reason they don’t use that word!
Google has a public project for sanitizing native machine code for untrusted direct execution, and Go shipped with it as a compiler target — Google’s not aiming for the common runtimes.
My question on Go is: “Who cares?” What’s unique about Go, and what’s it targeting? Who is the market? Native code is only relevant for driver writers: are they targeting that market? If not, do they honestly expect a widespread move away from existing platforms (JVM, CLR)? Or is there some niche they’re targetting that I’m missing (a la Erlang’s targeting of telecom hardware)?
Robert: Google has two primary use cases for Go:
The first is to replace C++ for internal services and middleware that make heavy use of Protocol Buffers. Impedance mismatch with protobufs is probably the primary reason that Go didn’t ship with heterogeneous lists or discriminated unions (much less GADTs). Go is extraordinarily well-suited to Google’s service idioms.
The second is for writing code that targets NaCL in the browser (which ships with Chrome), where you compile to a safe/sanitized subset of word-aligned machine code. Go’s runtime/libc is tiny compared to any other language of it’s expressivity, and the CSP model is very good for handling things like DOM events, much better than interrupts/signals.
Go ain’t bullshit. I plan on writing/porting some NaCL apps in using it myself, in advance of the release of ChromeOS.
I don’t think it’s bullshit. I never said that. I just don’t see the point, and I’m still not sure I do. While I didn’t know about NaCL before, I’m a bit cynical, particularly considering the rise in the mobile space on the web and the general entrenched nature of HTML. So maybe we’ll see NaCL+Go take on Flex…but that’s going to involve a lot more than a pretty language. JavaFX is pretty much completely failing to get into that space, and I’m not sure if Google is going to have much more luck.
Of course, I’m traditionally cynical about all the major popular advances in computing, so maybe this is a good thing for Google and Go.
Pointlessness is equivalent to bullshit in my book.
NaCL will be extraordinarily important for ChromeOS, but the media have completely ignored it — think about being able to use emacs/vim/scite for
<textarea>s in any page, having native ssh/vnc/rdp clients, having usermode-linux in a browser tab, games!On the periphery I see it being useful for concurrency pedagogy, more than any other language I’ve seen. The existing industrial languages that are otherwise suitable have tons of baggage: Scala, Clojure, Haskell, and Erlang are all idiosyncratic in their own ways, and all have major features that have nothing to do with concurrency. Go is extremely straightforward, and its only oddities outside CSP are omissions and rationalizations aimed at cleanliness. Having Google’s backing makes it a much easier sell to students and committees than any academic language.
@Fred
Did I say pointless? I just asked questions. I said that I didn’t get it. And I still don’t.
The problem with native code in a browser is that native code isn’t portable. This is why VMs came into existence in the first place, and why all web languages work within a VM of some flavor of another. If Google’s come up with a way to solve that and do so in such a way that it’s widely adoptable, then *maybe* they can start to dig into the space held by Adobe. Maybe. So far, any effort to fundamentally transform the web (including Google’s relatively straightforward Gears, and program-normal-application-code-for-the-web like applets/webstart/JavaFX) haven’t managed to make enough market penetration. Flash comes closest, and even that is a firmly second-tier technology to what us old fogies once called DHTML. So I’m still pretty cynical that even Google can make headway into the world of the web.
At this point though, all I have to point to is my cynicism and the web’s poor historical track record with new tech adoption. All you’ve got is your excitement and promises. So we’ll have to see what time tells.
As usual, whenever I post something people actually comment on, God arranges things so that I don’t have time to respond for a couple of days. Which is especially annoying on this posting, as it seems that people missed my point.
This isn’t a general critique of Go. First off, I haven’t looked at the language enough to provide a general critique. Second of all, I generally prefer praising good languages and explaining why they’re good, than flaming bad languages and explaining why they’re bad. If you can accomplish the same goal except positively instead of negatively, that’s an advantage- and by focusing only on the negative it’s very easy to slip into the crime of cynicism, where everything sucks and nothing is good. At which point you have to ask why you don’t just end it all, and put yourself (and everyone else) out of your misery.
This is, however, a specific critique of Go- and C, C++, Ocaml, and Javascript. Of a specific anti-pattern I’ve detected in a large number of languages.
Let me explain what I’m talking about using a concrete example. Go has pointers. Why? I mean, even Java has referential transparency. The “advantage” that pointers bring is they give the programmer the ability to control whether or not a given object is boxed (i.e. allocated on the heap as a seperate object) or not. But this is just an optimization- changing an object from being boxed to being unboxed (or vice versa) is an optimization. And not a very important one, at least if you have a copying GC. But the cost of allowing the programmer to make this decision is an increased cognitive load on the programmer- for each object the programmer has to decide or determine if it’s a reference or the actual object. By itself, this isn’t that hard- but it’s in addition to all the other things the programmer has to be thinking about all the time. It’s hard enough to remember you’re there to drain the swamp when you’re up to your ass in alligators- adding more alligators for you to wrestle doesn’t help. My point isn’t resting on, or even commenting on, the big features of Go, like Go-routines. It’s resting on the small features of Go, like the existence of pointers.
That said, some specific responses:
Fred, you said:
I’d be interested to know why, as you comment, “Erlang ain’t it”. From where I sit, the three main advantages Go has over various competitors like D and Erlang and Cilk, is threefold: 1, Google, 2, Brian Kerninghan, 3. Rob Pike. In this sense, Go is like Arc, in that the main reason everyone is gaga over it is who is behind the language, not the language itself. If you or I had released Go, it’d have disappeared without a ripple, and no one would care about it. If you don’t believe me, ask Walter Bright. Who’s he? Precisely. (hint: google “D programming language”)
I am more than a little bit disturbed at the implication that the only way for a language to gain widespread adoption is to have it backed by a large corporation. For two main reasons- one, that large corporates are by nature exceedingly conservative (they’re already at the top of the heap, they want to stay there), and that thus the most interesting and innovative languages will not be backed by major companies, and two, they are driven primarily by the profit motive- if it’s profitable for them to screw over the developers of the language they control, not only is there is a large temptation for them to do so, there is arguably a legal requirement (aka fiduciary responsibility to their shareholders) to do so. If we’re going to be corporate lackeys, then I say let’s go and be Microsoft corporate lackeys, and at least get F# out of the deal.
Yes, as far as I’m concerned, being owned and pushed by a big corporation is a minus for a language. Languages should be open sourced and publicly standardized (ANSI, ISO, ECMA, pick one).
Are you seriously suggesting that Go is not idiosyncratic in it’s own way? Or that every feature has to be related to concurrency? Including all of Go’s features? Or is it simply that they’re different from what you’re used to? Every programming language of sufficient age and complexity to be interesting is going to be idiosyncratic.
What you say about variant types (aka discriminated unions) being left out of the language because they don’t mesh well with Google’s protocol buffers is also very disturbing. There are a number of things that a language designer needs to be thinking about. And there are reasons, I suppose, to not have variant types in your language. But this is not a good one. OK, Go may be the “perfect” language for Google, but I am not working for Google. Species which are over adapted for their specific ecological niche tend to do poorly when that niche changes, and the same happens to languages.
It’s not apropos to this thread, but untrusted native binaries will run on my computer over my dead body. Running untrusted heavily sand-boxed virtual machine code on my computer is bad enough. All it takes is one flaw in the “sanitizing” for my machine to be totally pwned by untrusted code. Maybe windows users don’t give a shit anymore, and just assume that having your machine taken over and turned into a bot is just normal, but I still care.
Re untrusted native binaries: trust comes from several different sources. Most important is the provenance of the binaries; other sources of trust include verifiable signatures, simple static analysis, and dynamic runtime analysis.
FWIW, I’ve been running Windows since 1993 and haven’t had a virus or infection of any kind since 1996. I don’t habitually run a realtime virus scanner. I just occasionally to verify machine cleanliness. More importantly, I’m aware of all the background processes that’s normal for my machine, so I notice anything new (usually it’s auto-update / warm start crapware from Adobe, Apple, Sun, Google, MS, etc.). I monitor their actions via Process Explorer, and I can check that they are all signed and all the modules they load are signed. I can also verify the signed status of every loaded kernel module this way. I also occasionally run a rootkit checker (like Rootkit Revealer) to check that the monitored view isn’t different to the actual view. I never run Adobe Reader, Flash or Java in the browser by default – PDFs are set to save to disk and opened with Foxit, and I run Flashblock. I don’t allow Java flat-out – I’ll load a web page in Chrome to enable Java if necessary. Flash is the only media-playing plugin I permit in the browser process, with things like Realplayer, WMV, Silverlight, Quicktime etc. all disabled.
As to your comments on boxing: boxing in Go is transparent. Ints assign freely to an empty interface. The following code works as expected:
package main; import "fmt" type Foo interface {} func main() { var x Foo; x = 24; fmt.Printf("%d\n", x); }Variant types are a form of polymorphism limited to the forms described at the definition site. Go’s polymorphism through interfaces is more general, but seems to me to be able to provide the functionality of discriminated unions with information hiding, though without pattern matching etc.
Pointers are not strongly related to boxing, IMHO. Pointers are about indirection, and are not limited to a single degree. Boxing is about polymorphism, so that value types (typically immutable) share a base type with reference types, for assignment compatibility purposes; it only ever introduces a single degree of indirection. In a GC environment, there’s no noticeable difference between an immutable reference type and an immutable value type, though of course the calculus changes dramatically when mutability enters the picture.
I’m not sure what you mean by boxing, but when I talk about boxing it is strongly related to pointers. When I say something is boxed, I mean it’s separately allocated on the heap, and you have a pointer to it. When it’s unboxed, there is no pointer. So, if an A holds just a single B, and a B is 100 bytes in size, how large is an A? If it’s 100 bytes in size, than the B is unboxed. If A is only 4 or 8 bytes in size, then the B is boxed- and what A has a pointer. Note that both boxing and unboxing can be over multiple levels- an A can contain a B which contains a C which contains a D and so on. And each of those “contains” can be either boxed or unboxed.
Fred Brooks talks about the difference between accidental and essential complexity- I think a better term than accidental complexity would be unnecessary complexity. Accidental complexity implies that no one choose to include that complexity- perhaps Fred Brooks couldn’t envision programmers deliberately complicating their programs for no or little benefit. And yet, this is exactly what we have here. That A contains a B is essential (or at least, much closer to being essential) complexity than how A contains a B (boxed or unboxed). It is quite possible to design popular, heavily used languages wherein the programmer doesn’t have to worry about whether B is boxed or unboxed (whether A holds a B, or just a pointer to a B). Java is an example of this. In at least one way, Go is a step backwards from Java. Go has (re-)introduced some accidental/unnecessary complexity which had all but disappeared in modern languages- very few languages developed and popularized in the last two decades do not have referential transparency. Why did Go buck this trend? The only answer I can think of is to allow the programmer to hand-implement a particular optimization (specifically, unboxing complex data structures).
Fred Brooks was brilliant in choosing the word “accidental”.
First of all, he was pulling from Aristotle’s substance theory (more at Wikipedia), which means he doesn’t have to reinvent the categorical wheel.
Second (and somewhat more importantly), the term unnecessary is an artificially and unnecessarily loaded term, in the sense that it implies you could wipe out the accidental quality without replacement and be fine. Especially when talking about software complexity, that’s not true. For instance, consider compiling the software. The compilation step is accidental complexity: that is, it isn’t essential to the problem being solved. But anything that’s not raw computer code needs to be compiled—you can’t escape it. Even the expression of the problem in a language is accidental complexity to the solution, but it’s not avoidable.
Unless you’re targeting operating systems and device drivers (where magic memory mangling is necessary), pervasive pointers1 seems painfully stupid. Doubly so if it’s for the express purpose of allowing users to hand-roll optimizations that the compiler can/should be responsible for.
In programming languages, waste is haste, and that’s to be construed as a good thing: see Paul Graham’s “The Hundred-Year Language”. Or just notice the widespread adoption of slow languages like Ruby, Groovy, and JavaScript. Even Java was laughed out of the running as a “real” language because it was too slow when it first came out.
1 C# has a nice “drop to pointers” mode (appropriately called “unsafe code”), which is probably the nicer way to do things if you absolutely must.
I disagree. “Mythical Man Month” was not written in the philosophical vein, nor was it’s target audience one that could be confidently relied upon to know about Aristotle’s definition of accidental (I didn’t until I read that wiki article), nor was Aristotle a major figure in the book (or even mentioned, to my recollection). In such a circumstance, the colloquial and not the technical-philosophical meaning is the most natural reading. Or, to translate this erudite, polysyllabic expostulation into the vernacular: you’re full of shit.
A complexity become unnecessary when it becomes possible to avoid it. We’re not mathematicians where we can stop one we demonstrate that a solution exists- we actually have to find the solution. This means there are certain complexities which, while theoretically unnecessary, are necessary in practice. On the other hand, it makes a great deal of sense to me to pick a language which maximizes the total amount of complexity I can avoid.
With regards to NaCl, I remember the last time some large corporation got all hot and bothered about downloading general-purpose executable code and executing it in the browser. They at least had the sense to us their own virtual machine, and to design it explicitly from the ground up for code verification- something which the x86 architecture was not designed for. This also gave them the extra added advantage of portability across multiple architectures. This was, of course, Sun Microsystems and Java applets. Of course, a series of security flaws led people to conclude that the whole concept was a bad idea, and it was abandoned. Feel free to explain to me why NaCl will sidestep the problems that killed Java applets. But until then, I find the name ironic, because I recommend taking NaCl with a very large grain of salt.
NaCL has some odd security properties: it is effectively a bitcode VM that happens to directly use your physical processor for execution (after full code verification). It doesn’t let you do unaligned pointer arithmetic, or real interrupts, or make any OS syscalls. It provides its own well-considered syscall-style API, and that’s the only communication mechanism you get.
Unfortunately it does suffer from a most intractable problem — timing attacks. Because your code runs directly, you can do things like detect the contents of the CPU’s caches by brute force. Researchers have written timing-based exploits for NaCL, but noone’s found any other ways through the sandbox.
The JVM’s massive girth and added latency make this a lot more difficult due to all the introduced noise, but it’s still possible. That girth also makes it incredibly hard to write nice applets in.
Go has machine pointers but they aren’t what you think they are:
Dereferencing is automatic in common cases
No pointer arithmetic is allowed
No such thing as “undefined behavior” — it is possible to manually construct an invalid pointer, but dereferencing one will always cause the runtime to halt
They’re present because you’re going to have to tackle the reference/value distinction somehow, and Go is intended to compile to machine code. By exposing them, the user can write tight code that making strong assumptions about how data structures are copied, and what kind of memory footprint will result.
Pointers are not “just an optimization” — They’re a lot like Tail-Call Elimination — theoretically it’s just an implementation detail, but it’s one that has a massive impact on programming style. Any code that takes advantage of it is invalid in its absence and immune to refactoring it out.
It combines lots of parochial weirdness from both Smalltalk and Prolog (message-passing, image-based live-in sandbox, sentential syntax, etc.) making it very inaccessible to industrial programmers. OTP’s reliability is perfect, but only if you use it as Ericsson intended: it falls over easily if there’s any impedance mismatch in how you architected your system.
Erlang is obviously successful, and will continue to be so for quite a long time, but just in a very narrow category of infrastructure applications.
I already knew all about Walter from having explored D, which seems to have been an obvious failure nearly from birth — noone cares about a marginally less-fucked version of C++, especially not one controlled by a proprietary compiler company. Cilk manages to apply that same proprietary stink to a project that already had the innate unsuccessfulness of private academia (see Cyclone for a language that never even made that leap).
It’s uncouth to apply the Arc slur to a language that shipped with two full compiler suites targeting many native architectures, with it’s own complete standard library. Arc is ~100kb of shitty macros that only work on an old version of MzScheme and break its string support.
I’m saying something else: that a language backed by a tiny corporation is meaningless. I know of three things that can really get a language off the ground: early total openness, backing by a megacorp, and multiple independent implementations.
Have public standardization bodies ever been anything but facilitators for corporate bullying? I guess there are languages for which standardization has been a tombstone — Smalltalk multiple times!
Of course it is, but save for CSP (and maybe interfaces), its oddities are ones of omission and clarification. It is so very familiar.
If you’re trying to teach concurrency, it helps to be able to focus on that alone and not have to address issues that are really about the platform or syntax or evaluation idiom.
You would understand if you’d ever taught Haskell to undergraduates :)
Technically what Go has is references, like Pascal and Algol-68, not pointers like C and C++. Yes, I know this. I’m old enough that they taught me Pascal back when they were teaching me to program (and I still have both my copies of “Oh! Pascal!” on my shelves- good books).
By the way, there are a lot of languages that compile to native code that don’t have the pointer/value distinction- Ocaml and Haskell being two. Oh, and all those Java native mode compilers, like gcj.
This is exactly what I’m talking about! It not only allows the programmer low-level control over how the data structures are laid out, and require the programmer to control how the data structures are laid out, whether the programmer wants to or not!
I’d love to point out a compiler that unboxed non-trivial data structures if the compiler thought it worthwhile, but I honestly don’t know any- primarily because it’s generally not worth it. If you have a copying garbage collector, then if A has a reference to B, generally A and B will be near each other in memory. So you get most of the cache locality advantage of unboxing B into A right there. You save some memory- you have the pointer in A and the per-object overhead of B you could eliminate. But just as often you lose memory, as this forces you to keep multiple copies of B around instead of sharing one B (this is especially true if B is immutable and thus more easily shared). And you complicate the garbage collector, because now the GC has to look not only for pointers to A, but also to pointers to the B part of A. And what happens if a reference to the B part of A outlives all references to A- do you hold on to A as well? The cost/benefit ratio is just too large.
I agree that tail call optimization and pointers both have large impacts on the programming style. So does limiting variable names to six characters. Not all things that impact the programming style are good.
Tail call optimization is about correctness in the face of the lack of language features- not about performance. Loop constructs tend to be imperative in nature. And they also tend to accrue more complex exit conditions and variations- for vr.s while vr.s repeat vr.s do-while loops with return, break, continue, next, previous, etc. continuations. So functional languages tend to ditch looping entirely or severely limit it, and use recursion instead (every loop can be implemented as a recursion). This allows the languages to avoid the complexity most pragmatic imperative languages end up with in their looping constructs. If you had an infinite stack, you wouldn’t need tail call optimization to do your looping with recursion- but since you don’t, tail call optimization is necessary. Not for performance. For correctness. What’s the similar story for Go- what advantage (other than requiring the programmer to lay out his data structures and deal with boxing explicitly) does Go gain from having pointers?
Mental note to self: check for pending comments and approve them before replying. My apologies, I didn’t mean to ignore your comment, I didn’t see it.
Funny, this is exactly my opinion of Go. Of course, the problem here is:
and:
And then there is this comment:
To my knowledge, I haven’t mentioned teaching at all in this discussion- and it certainly wasn’t what I was thinking about. Yes, Haskell is a terrible teaching language. But then I also think that about Ocaml, Java, and C++. Maybe in their senior year, but not before then. You don’t learn to fly in a 747 for F16, despite the fact that those are what the professionals fly.
So, for an industrial language to catch on, it has to be:
Given these requirements, what you’re going to get is slightly less fucked-up C++s. Always. Especially from the perspective of someone who is quite willing to roam farther afield, and who doesn’t think that smalltalk is too weird to be learned.