5 ms·
Except for C, no popular language (modern or ancient) has ever used header files. So it's not a story of "abandoning", more like an insane C idiosyncrasy that
by otabdeveloper4 3y ago
Except for C, no popular language (modern or ancient) has ever used header files.
So it's not a story of "abandoning", more like an insane C idiosyncrasy that wasn't ever used anywhere else.
- stabbles 3y agoIn Python I sometimes miss "header files". Quite often you see: import x class Foo: def bar(self): import y # break circular import y.something() Would be nice to be able to import `Foo` without pulling in also `y`, or moving `y` inline. Can be solved in different ways, but you see inline imports everywhere.
- colejohnson66 3y agoThat's not a problem with modules, per se, but a problem with Python's module system. Importing a module in Python just executes the whole file. JavaScript used to have this issue with `require` years ago. Each `require` statement essentially executed that import right then and there, and would bug out if circular dependencies existed. However, they fixed it with `import`. Almost every other module-based language does not have issues with circular dependencies. Python, in theory, could follow their lead, but they won't. EDIT: I wasn't as clear as I could be. The issue again isn't modules, but scripting languages that allow top-level statements at all. Intermixing types and method declarations with executable code makes circular decencies an issue. Compiled languages don't allow top-level statements, so they don't have the dependency resolution problem.
- paulddraper 3y ago> Each `require` statement essentially executed that import right then and there That still happens. It takes a lot of magic trickery to make cyclical require/imports work for JavaScript and a lot of times they don't/can't.
- colejohnson66 3y agoThat's true. I guess I misspoke and wasn't as clear as I could be. What I meant to say was: the problem with Python and JavaScript's module system isn't modules themselves, but top-level statements. You are correct that `import` doesn't solve such a problem; It solves the synchronous dependency resolution problem. Realistically, that's just what happens when a language allows top-level statements, as they are executed when a file is loaded. As such, scripting languages tend to fall victim to the problem, but compiled ones don't. I'll update my comment.
- rezonant 3y agoWorth noting that ES-style imports do improve the scenario for cyclic dependencies though, assuming you do not need to reference the symbols imported during the execution of the top level statements. This is because `import { bar } from './bar.js'` makes `bar` a "live binding" -- even if `bar.js` also imports `foo.js` and thus `bar` may initially be undefined due to the cyclic dependency, later when `bar.js` completes its initialization of `bar`, the identifier `bar` in `foo.js` will be updated to match. So if you just need to call that binding within, say, a function or a method call, its typically fine to do so assuming that by the time that function or method call is executed, both modules will have completed their initializations.
- paulddraper 3y agoAnd that is similar to require's behavior for properties on the returned object. const foo = require("foo"); // starts out as empty object exports.bar = function() { return foo.fn(); }
- rezonant 3y agoYes but the difference is that this: let x = require('foo').x of course, cannot do that. It's a small difference but it does make it easier to fall into the happy path in more cases.
- paulddraper 3y ago
- lelanthran 3y ago> Almost every other module-based language does not have issues with circular dependencies. I recall having seen a circular dependency compiler error in Go.
- melagonster 3y agowait, is this even possible? I just presume that all import statements are executed first.
- jpc0 3y agoIronically you can solve this with header guards in python as well... Old problem requires old solution
- colejohnson66 3y agoIt was only ever a thing in the first place because, almost half a century ago, some people decided to write a one-pass compiler due to storage/memory limitations. Even 40+ years ago, C was the only popular language that required them - to never address the issue is practically inexcusable.
- uecker 3y agoI personally like headers very much and do not understand the hate: They are super simple and efficient. They do not work well C++ though because it puts the implementation into the header for some reason.
- pjmlp 3y agoThe reason being the compiler needs to know the template code to be able to generate a specialization for it. ELF files based on the C type system can't provide such information on object files, and binary libraries. In the modules world it can read it from the symbol table on the BMI, like in any other module based language.
- uecker 3y agoNot sure what ELF and C type system has to do with this. C type information can be stored in ELF (as well as many other things). This is how a debugger can know the types. It is simply a language design flaw of C++ that class implementation (not just templates) ended up in the headers.
- rob74 3y agoso modules should have actually been one of the first feature added to C++? Because in C the usage of header files is still more or less sane, it's only C++ that drives it too far...
- pjmlp 3y agoImpossible given the original design requirements for C with Classes, while being developed at Bell Labs, require nothing more than a standard UNIX C toolchain. Hence name mangling, the only way to add some linking type safety, while using a bare bones UNIX linker that only knows C and Assembly.
- phaedrus 3y agoOne justification I've seen is that the header defines the interface of a library and allows its implementation to change; the argument reads remarkably like that for pure abstract base classes or what other OOP languages call "interface". The problem, of course, being that C's version of this idea is a bastardized compile-time-only polymorphism. But from a developer user experience standpoint, what is the difference between having to write things twice for this reason: // header.h int foo(); // library.c int foo() { return 42; } Versus writing things twice for this reason: // interface.cs interface IBar { int foo(); } // library.cs class Bar : IBar { int foo() { return 42; } } Granted in C it was for dumb reasons whereas in a modern language it's for better (?) reasons, but: You're still writing things twice!
- Koshkin 3y ago> writing things twice Not in Oberon.
- 9659 3y agoThe ada concept of "package spec" and "package body" does headers the right way. Expose the types, and the public procedures. Users only need to see the spec during compilation.
- layer8 3y agoPascal has include files which work very similarly. Cobol has the COPY statement which is commonly used to include shared data structure definitions (“copybooks”).