Hacker News new | ask | show | jobs
by mike_hearn 1036 days ago
The no-circular-dependencies rule was a huge pain though. It allowed the compiler to be very fast (along with it basically not optimizing), but no other language has copied this because combined with weak to non-existent refactoring tools it was just a constant pain to be hitting this limitation whilst developing. Sure, if you're an architecture god who plans out all their internal interfaces in advance on paper it was OK, but I was 15.

Delphi could have potentially navigated into the web era quickly enough, but was slow to do so. It was essentially a Windows product at its core. They made a half-hearted attempt to port it to Linux but did so using Wine(lib) which back then was very rough, so Kylix had a poor UX and of course the problem was that the Delphi widget toolkit was the Windows toolkit which Linux didn't have. IIRC it was also quite slow to even get things like an HTTP stack, which had to be produced by a third party company.

The focus on visual componentization back then was kinda great though. That's definitely something that went AWOL somewhere along the line. The good database integration is also sorely missing in more modern languages and frameworks.

5 comments

> The no-circular-dependencies rule was a huge pain though. It allowed the compiler to be very fast (along with it basically not optimizing), but no other language has copied this

C++20 Modules, .NET Assemblies, D modules, Ada packages, and plenty of other othes.

Also, Turbo Pascal did indeed allow for circular dependencies between units, as long as the related uses statements were written in the implementation section of the unit, and there were no public dependencies.

Thanks to bitsavers, Turbo Pascal 5 manual, page 97,

http://bitsavers.informatik.uni-stuttgart.de/pdf/borland/tur...

Yeah, this... You can have circular dependencies, as long as it is in the implementation only. Takes some thought on interface definitions to avoid these things. Sometimes it's a real pain.

There's also the issue that when you split out units, and then you have a user who wants to consume say, a library you've written, you then have to document "Ok, to use this you have to use X, Y, and Z units for type definitions"

A better approach is to have a single "entry point" unit if you will, that simply re-declares all of the types from the X,Y,Z units, so that when you go to use the code you've written, you only have to import W, and get all the type defs already. (Hard to explain what I'm talking about I guess)

IIRC, pre-Object Pascal Pascals had a "forward" declaration using which you could call a function or procedure before defining it, as long as it was defined somewhere below in the program. I don't remember if it supported circular dependencies or not.
Modules and assemblies are different. Lots of languages ban circular dependencies between modules (most!). Delphi units were single source files though, and that's a much more painful proposition because it forcibly links source file size and program architecture. I saw a lot of big unit files in Delphi code bases.
Arriving at a place with a Delphi codebase developped over the course of 30 years (some files had 80ies copyrights), the tendency tot have enormously long ones was apparent. 10k lines would be short, and 100k nothing unusual. Really hard tot navigatie, the Delphi IDE doesn't really help with that (or much else really).
Apparently no one read the part about dependencies being allowed between implementations section, and I imagine also never looked into Delphi packages, in regards to code size and organising units.
I recall being allowed to have dependencies between implementation sections, but this was still quite a painful restriction.

I don't remember Delphi packages, indeed. It was a long time ago now.

>The no-circular-dependencies rule was a huge pain though. It allowed the compiler to be very fast (along with it basically not optimizing), but no other language has copied this

Go forbids circular dependencies.

Yes, and I usually find when I run into this that I should be doing something differently in our package/interface design. I think it's overall very helpful though also annoying to detangle if you the cycle is deep enough.
indeed it does; i've run into it a few times. just last week i was writing some go code in vscode and it wasn't working because i had a circular dependency. vscode was reporting something in the "problems" window like a "go list" failure due to "no go files". i couldn't make head or tail of it for a few minutes. it took me some time to note that the path listed in the issue was one i had just edited. in fact, i didn't even notice the path at first -- i tried running `go list` from my repo root and didn't see the error, but when i saw the path and changed to that dir and tried again, i saw the `go list` error as reported by vscode, and i think that when i opened the file i had just edited in vscode it displayed a more understandable cyclic import message in the offending import, and it was easy to go from there.
Life is better without circular dependencies
I was just thinking how the ideas behind Turbo/Borland Pascal and Delphi reminded me of Go, I know it should've been Dotnet, but Dotnet got complicated right from the start with it's deployment model (lots of dlls, assemblies, blah, blah blah).
> They made a half-hearted attempt to port it to Linux but did so using Wine(lib) which back then was very rough, so Kylix had a poor UX and of course the problem was that the Delphi widget toolkit was the Windows toolkit which Linux didn't have.

I think the UX was fine as it was made to work with KDE1/Qt1 which was very Windows-like (in appearance and behavior) at the time, but the real issue was that instead of trying to make VCL work crossplatform (like Lazarus' LCL does) they made a different framework (IIRC it was called CLX) which used Qt1 and was very incompatible.

They released a free version with the limitation that your program had to be GPL, though at the time it was fine as GPL wasn't the boogieman it is today. Sometimes i wonder if Borland had released the entirety of Kylix under GPL and thus gained mindshare among open source programmers at a time when Linux had started becoming more popular among programmers if things would be different for Delphi - if nothing else it wouldn't be seen as some ultra-expensive tool that people only use because they have no way to move away from. At the time Borland made money from enterprises (which would buy the expensive enterprise version anyway) and proprietary desktop software developers (which wouldn't use the GPL version anyway) so having Delphi as a GPL'd program wouldn't affect them much but instead have the tool's mindshare expand and even have developers help Borland fix bugs with their framework and improve their compiler.

I was thinking that a couple of years ago when i tried the free version of Kylix under my Linux installation and it failed to work with all sorts of errors despite my efforts - but errors that should be simple to fix if the source code was available. Then i tried it in a VM running some Linux version from the early 2000s and it worked, which made me think how nice it'd be if it was a GPL'd software i could modify.

After that nostalgia session, i deleted the VM, downloaded the latest version of Lazarus and fixed the Gtk1 support[0] because i could as that is open source (ok ignore the broken images, that is because the shot is from a few months ago when i tried to remove the gdk_pixbuf dependency, normally they look fine but that is the only screenshot i had around :-P). FWIW the fixes were merged in (and i made some extra fixes since then).

[0] https://i.imgur.com/su9JCUY.png

> Sure, if you're an architecture god who plans out all their internal interfaces in advance on paper it was OK, but I was 15.

Us Architecture Gods don’t need planning. The internal interfaces have been immaculately conceived in our mental space. It’s the regular peasantry of architecture that need to do the planning.

F# requires code to be in dependency order. You can't refer to types unless the compiler has seen them.
Yeah, this is quite nice once you are used to it.