Hacker News new | ask | show | jobs
by jchw 975 days ago
Hmm meh. I have a soft spot for Delphi/Object Pascal but I think the case here is not great. What it looks like at a glance is they wrote a better Pascal program than the Go one it was competing against, rather than just idiomatically port it. A fine approach, but it doesn't tell us that much. Specifically, it doesn't tell us very much about programming languages.

Go has plenty of weaknesses versus Pascal, but two commonalities of the languages are lightning fast compile times and a pretty good experience for modelling data structures. Pascal is undoubtedly lower level and does not guarantee memory safety, whereas Go does but its GC is often less efficient and more memory-heavy than manual allocation.

Blow for blow, though, I'd say the largest weak point for Pascal is a somewhat archaic syntax and for Go, honestly, the concurrency model. (Channels are nice, until they are not. I feel as though it's easier, though not necessarily easy, to write correct programs using mutexes than Go channels in many cases. This is weird, because nothing has changed about the old shared memory with locks model since it was the source of so many problems. Yet, programmers, computers and toolchains have changed a lot. Rust with locks is a great example.)

But the biggest problem for Pascal is the lack of a strong killer app. Back in the day, libraries like VCL made Delphi amazingly productive for desktop apps. But VCL/LCL doesn't really hold up as well these days, where desktop apps are less important and the important features of GUIs has shifted a lot. That leaves Delphi and Object Pascal as a sort-of also-ran: It's not that Go is especially good, in fact I'd argue its claim to fame and namesake (the concurrency model) just wound up being kind of ... bad. But, now that it's here and popular, there's little reason for e.g. Go developers to switch to Object Pascal, a less supported language with less of a job market, less library support, etc.

And that really is a shame, because it isn't really a reflection of Object Pascal being unfit for modern software development.

4 comments

"Channels are nice, until they are not. I feel as though it's easier, though not necessarily easy, to write correct programs using mutexes than Go channels in many cases."

The rule for mutexes is, never take more than one. As long as you only ever take one, life is pretty good.

When all you had was mutexes as your primitive, though, that became a problem. One is not enough. You can't build a big program on mutexes, and taking only one at a time.

But as you add other concurrency primitives to take the load off of the lowly mutex, and as you do, the mutex returns to viability. I use a lot of mutexes in my Go code, and I can, precisely because when I have a case where I need to select from three channels in some complicated multi-way, multi-goroutine choice, I have the channels for that case. The other concurrency mechanisms take the hard cases, leaving the easy cases for mutexes to be fine for once again.

The story of software engineering in the 1990s was a story of overreactions and misdiagnoses. This was one of them. Mutexes weren't the problem; misuse of them was. Using them as the only primitive was. You really, really don't want to take more than one at a time. That goes so poorly that I believe it nearly explains the entire fear of multithreading picked up from that era. (The remainder comes from trying to multithread in a memory-unsafe language, which is also a pretty big mistake.) Multithreading isn't trivial, but it isn't that hard... but there are some mistakes that fundamentally will destroy your sanity and trying to build a program around multiple mutexes being taken is one of them.

(To forestall corrections, the technical rule is always take mutexes in the same order. I consider experience to have proved that doesn't scale, plus, honestly, just common sense shows that it isn't practical. So I collapse that to a rule: Never have more than held at a time. As soon as you see you need more than one, use a different mechanism. Do whatever it takes to your program to achieve that; whatever shortcut you have in mind that you think will be good enough, you're wrong. Refactor correctly.)

> The rule for mutexes is, never take more than one. As long as you only ever take one, life is pretty good.

Is there any shortcoming you can't apply that to? Don't malloc unless you free. If you cast in your program, make sure to cast to the correct type.

> The story of software engineering in the 1990s was a story of overreactions and misdiagnoses. This was one of them.

The problem of multiple mutexes was diagnosed well before the 90s. "Dining philosophers" was formulated in 1965.

    https://www.adit.io/posts/2013-05-11-The-Dining-Philosophers-Problem-With-Ron-Swanson.html

    https://www.adit.io/posts/2013-05-15-Locks,-Actors,-And-STM-In-Pictures.html
Yeah, I can relate to this. When coding in Go I routinely combine mutexes and channels. Channels are useful for signalling and some other things. I am a bit miffed though since it absolutely feels like CSP concurrency and message passing COULD be better, it's just not in Go. Maybe I'll try Erlang some day and be imbued with the curse of knowledge.

Rust doesn't solve the problem of multiple mutexes being tricky, but it does at least solve most of the other problems with sharing memory. To gain a little more assurance with Go, I do sometimes use the checklocks analyzer from gVisor, which gets you some of the way.

"Maybe I'll try Erlang some day and be imbued with the curse of knowledge."

For the purposes of this discussion [1], Erlang is just Go except you don't have mutexes as an option at all, so anything you want locked has to be in a separate Erlang process (analog of goroutine). So if all you want is a shared dictionary to be used as a cache or something, it has to have its own process/goroutine, you don't get an option of just locking access.

Since that's how it works in Erlang, it has a bit of syntax grease around it, but not enough to make it just right; you've still got to do things like handle communication errors because it could be on a different node in a cluster whereas in Go it's just a local shared resource.

I think the main problem is that as useful as actors are as a concept, they're not a great foundational abstraction, which is to say, the base that everything is built on and you can't go below. It works, but then it means you're paying the full actor price for everything. But you don't always want the full actor price for everything, and you don't need to pay it because in practice "lack of actor isolation" is rarely the root cause for any particular problem, because that's too big a thing to be the root cause.

[1]: If that's too glib for you: https://news.ycombinator.com/item?id=34564228

> The rule for mutexes is, never take more than one.

Ideally the actual rule is, never take a mutex while holding another mutex. You can take multiple mutexes simultaneously if the API supports it. (The problem is the API usually doesn't support it unless you implement the mutexes yourself using lower-level primitives, but that requires not actually using mutexes as your primitive.)

( > the technical rule is always take mutexes in the same order.

As you note, this doesn't actually work in practice, since you've given youself the opprotunity to get the order wrong every single time you do it.)

Mutexes aren't OK even if you only use one. They are error prone, you can forget to unlock. And of course, there is a temptation to avoid using it for efficiency reasons, because you "know" this part of the code is safe.

These days I develop servers on the JVM. We almost never think about mutexes or related things, libraries take care of that. I use Scala, and our entire data model is immutable, eliminating most race conditions. I think I had to declare something as volatile once or twice.

> They are error prone, you can forget to unlock

That's not a problem with mutexes but with resource management in some languages. In Rust mutexes use RAII and unlock automatically - you cannot accidentally forget to unlock.

Yes but it is very easy to hold mutexes locked longer than needed because you usually don't think about when stuff is dropped in Rust, you just let it go out of scope.

I have gotten really annoying deadlocks because of this in the past.

Fair point, but in order to make a deadlock, you need a reference back to the object that holds the lock. And back references in Rust are hard to make. Most of the time if you unlock too late, you get a performance problem, not a deadlock.
> They are error prone, you can forget to unlock

That's a language issue though, rather than a mutex one. It's reasonably straightforward to fix that, as some languages (like Nim) do.

> The story of software engineering in the 1990s was a story of overreactions and misdiagnoses.

This is a recurring theme, not one isolated to the 90s.

I think it's better to avoid time-related adjectives like "archaic" when talking about PLs. For starters it doesn't really mean much in general: in software development fashions come, and go, and then reappear as "new" again. Secondly, Pascal syntax is of the same historical age as that of C, and the ML languages. Would you say that C# (or Haskell) is syntactically archaic?
OK, how about "cumbersome" or "unnecessarily verbose". I haven't used modern Pascal, but the ancient version I used was also very limited.

I am 60, have used many languages, and used to love C and C++. I consider C and C++ archaic and Java is border-line. I thought Java was cool 10-20 years ago, but I've moved on to Scala.

It's verbose for sure (though Pascal's overall amount of boileplate probably takes less space compared to Java code). Structurally it's almost the same beast (both inherited from Algol), with notable exceptions like for loop.

Scala feels more modern than C to me too, when talking about language concepts/features. Regarding syntax they are in the same basket.

More people are familiar with Pascal and its syntax so I understand why people discuss it so much but it was succeeded by Modula-2, Ada, and Oberon which have better syntax.

One thing that is better about the syntax that I really like is the removal of BEGIN and END everywhere except around code in MODULEs and PROCEDUREs. IF/THEN/ELSE/END, FOR/DO/END, REPEAT/UNTIL, WHILE/DO/END, CASE/ELSE/END, no longer have BEGIN/END, even if there are multiple statements in them. This makes the code less verbose than C and C++ and equally or more compact vertically than them, depending on whether you put your braces on separate lines.

I'm not convinced: the term archaic implies that something is obsolete or subjectively old-fashioned, not just that it's necessarily old. C# isn't archaic in part because C never stopped being relevant this whole time and because C# syntax has evolved significantly in recent years.
It's not something to differentiate between them: neither is obsolete as long as somebody uses it to earn, and Pascal dialects has been evolving as well as C#, while neither deviated significantly from their roots. TypeScript (which is rather new PL) uses Pascal-style for types. Haskell, and Ocaml are niche too, and the syntax is 98% from 70s, but it still feels like a new for someone who sees them for the first time. And let's not even touch what hardcore Pythonistas think about what "old-fashioned" means.

Pascal syntax is non-mainstream nowadays, as well as Lisps, MLs, and lots of others, but neither fits to the word "archaic"

Sorry, I am not convinced. To me, Pascal's syntax and source code organization is archaic. Even more archaic would be COBOL and BCPL. On a somewhat similar level to Pascal, perhaps Erlang as well.

People's opinions do change over time, but I do not think it's likely that there is a renaissance awaiting Pascal's overall design decisions, specifically the ways in which Pascal differs from C. That's because differing from C is costly and requires justification. In some cases, I think Pascal's decisions just proved wrong; like the lack of short-circuiting in boolean expressions. I assume modern Pascal compilers have resolved this, but it's a good example of how I think things go: ideas that improve the status quo are worth bringing back, ideas that have a lot of switching costs are a hard sell.

I also understand that sometimes the difference may seem superficial, and that's because they are. Are Pascal units really vastly different from modern translation units/modules? Somewhat, but not that much. But it differs enough that someone not familiar with Pascal needs some time to adapt, whereas almost anyone with programming experience of any level can pick up Go, because it's stupidly simple. It does differ from C and other languages that are still contemporary, but when it does differ it's often good: the type syntax and declaration syntax is massively simpler than C, for example. (And yes, Pascal's type syntax is also better, at least by some measures.)

I'm happy to discuss these matters, but I will also be completely honest: I'm not particularly moved by this line of argument. It feels somewhere between semantics and an implicit desire to consider a future where Pascal syntax somehow becomes in vogue again as it was in the early 2000's. Maybe an evolved Pascal, but not the Pascal of today. And if I'm wrong, I'm wrong; I'm just calling it the way I see it.

> like the lack of short-circuiting in boolean expressions. I assume modern Pascal compilers have resolved this

I think your ideas on Pascal are a little outdated if you assume short circuit boolean expression are a modern Pascal compiler change. Turbo Pascal, one of the by far most common dialects, had that since the 80s, meaning this change has been around for four decades (assuming Turbo Pascal didn't take it from some other popular at the time dialect like USCD Pascal).

Delphi was really a "Concorde moment" in that it was actually rapid, both in terms of development speed, and performance, which was somehow forgotten as the web emerged.

Forgotten to the point that people thought Visual Basic was a good idea.

Delphi was great but a major deal breaker was that is was not free to use.
Early versions of Delphi were cheap enough ($99) even a high schooler could pool the money to buy it (or, more realistically, ask their parents :-P). That lasted until Delphi 5 IIRC.

The real dumb move (though TBH i can only say that in hindsight) was that they made a free Linux version with Kylix, the license required any programs released to be under GPL but they didn't release Kylix itself as GPL.

This was in very early 2000s, when GPL wasn't the boogieman among developers that seems to be nowadays with all the permissive licenses, desktop software was still something people wanted, commercial software wouldn't touch GPL and yet a lot of new programmers were onboarding Linux. Having Delphi/Kylix full GPL with a CLA (like some other projects) would mean that a) Kylix would become part of various Linux distributions, especially during a time when distributions were the main source for tools for Linux users, b) anyone working on FLOSS would both use and improve the tool, c) mindshare among programmers would improve as anyone will be able to try it out for free (as long as they used Linux, but many programmers - especially younger programmers at the time - didn't mind that), d) companies, enterprises, etc that wanted to sell shareware or just didn't want the rules GPL imposed would still need to buy the full program

Sadly this seemed to be yet another case of when Borland started losing touch with programmers in the 90s.

For students and hobbyist, yes and was probably a dumb MBA lead decision because it stops the developer pipeline.

OTOH, As many tool vendors will tell you its foolish to dismiss a tool simply based on price. If your paying your developers $100 an hour even tiny improvements in productivity can easily pay for a $1000 or more tool. Just the compilation speed alone vs C/etc is probably worth the 5+ mins a day in savings.

It is, but it stops a large number of people from being conversant with your tool and that in turn almost guarantees market failure. I've got a super luxurious toolchain on my computer because of the free software movement and it may not be as flashy as what you can get commercially but it suffices for all of my needs.
The later versions (somewhere after 2005) were also very buggy and sluggish. They produced fast code, but you really needed a powerful workstation.
With 20 years of hindsight I am not saying I can objectively talk about it but I kinda hated Pascal, with syntax and everything and so I didn't really try Delphi.

Visual Basic 5 was kinda awesome.

It may very well be that if I had looked at both again after, say, 5 years of programming I might have different views, but even now I still look back fondly on VB, it got shit done, esp if you needed a quick UI with not a lot of business logic.

Delphi (and probably also Free Pascal?) is not wholly memory safe, but at least it has bounds checking for arrays and a sane string type, which is more than you get with C...

I'm an ex-Delphi developer myself, and actually Go and Pascal are more closely related than you might think at first glance: Go code looks mostly like a C-family language, but the declaration syntax ("a int" instead of "int a") and the "package" concept which helps achieve fast compilation times are borrowed from Pascal. And both have a ":=" operator, although in Go it declares and assigns variable(s) with type inference, while in Pascal it's any assignment.