You're writing C, don't give me the "I might not know that what I'm calling is a macro" argument. You better know what you're calling in C. If you don't want to know what you're calling, don't write C.
You're writing C, don't give me the "I might not know that what I'm calling is a macro" argument. You better know what you're calling in C. If you don't want to know what you're calling, don't write C.
It's not uncommon to replace a function with a wrapper macro later on in an attempt to debug something or trace execution or whatever. It's not really realistic to expect that when such a replacement is done that they go analyze every possible call site to see if sizeof() results in side-effect evaluation or not.
This is just another case of C being a language built out of undefined behavior.
That puts a de-facto cap on either the speed of C development or the size of C projects.
It is definitely possible to have a project so large that no one person can understand it all. So either you can't have C projects larger than whatever that size is, lest you risk not knowing what you're calling, or you have to accept that developers working on your project will be reading and re-reading parts of source code that haven't changed in years in order to make sure they know what they're calling, rather than re-implementing common patterns in the codebase and trusting to prior diligence.
Either way, you're not selling C to me as a language to embark upon a major project with.
You don't need to know the entire project, but you had damn well better understand the code right in front of you that you are editing.
In literally any language, developers who don't understand what they are changing will cause bugs.
Macros (in any language, not just C) are the big problem, because you have to know a macro's definition before you can know precisely what its usage rules are.
Without macros, C projects can get as large as they like. Everyone must be required to know the language, but you can work on any plain-functional project given that knowledge.
Once you start #define-ing tons of preprocessor macro-functions, all bets are off; now your project is essentially its own language that every developer must first familiarize themselves with before usefully contributing, and that does indeed put "a de-facto cap on either the speed ... or the size" of projects.
Not true, see: https://doc.rust-lang.org/book/macros.html#hygiene (not unique to Rust, though, Scheme even has some of this like Hygienic macros)
Macros don't have to be C-style simple text substitution.
There is a reason there are no thousand-developer FOSS projects written in a Lisp; and there is a reason Ruby is derided for "monkey-patching"; and there is a reason that a single parse-transform in an Erlang project is a very strong code-smell that needs intensive justification.
The reason, in all of these cases, is that a project that includes one unique macro definition, is now a project effectively written in a different language—"language Foo, plus macro Bar." HN, for example, is written in Arc, a.k.a. "Racket + several macros." Emacs is written in Emacs Lisp, a.k.a. "MacLisp + quite a few macros." In general, if a project goes on long enough without any prohibition against adding yet another macro to the codebase, eventually that project—like Arc, like Emacs—will have accidentally created an entirely-separate language.
But before then—even on the commit of the very first macro definition—the prerequisite knowledge required to contribute to the codebase has now diverged: you can't just jump in as someone who "knows Foo" and immediately understand the logical flow of 100% of the code. To contribute to such projects, people have to learn how the macros change the logical flow—i.e. learn that diverged language.
To which they very likely will say: screw that! Thus capping the number of contributors, and/or the speed of development.
Yes; the main one being that about 996 of them wouldn't have anything to do.
> you can't just jump in as someone who "knows Foo" and immediately understand the logical flow of 100% of the code.
Can you just jump into the Linux kernel as "someone who knows C" and understand the logical flow of everything? USB, TCP/IP stack, virtual memory, file systems, block devices, scheduler, ...
I didn't mean "logical flow" to refer to following the branches of the code at runtime by keeping a model of the code's run-time state in your head. That does, indeed, require having a pretty solid grasp of the entire codebase (and, in large projects, is just rarely done for this reason.)
Instead, I meant "logical flow" to refer to, effectively, the ability to do static analysis of the code in your head: knowing which blocks of code are live or dead; which blocks might execute zero or one times, like a branch, or zero or more times like a loop; which blocks might execute immediately and which will be closed over and then thunked later; which blocks are within synchronization barriers and which aren't, and so will be touched by multiple threads; etc.
When you know a language, and you're debugging code written in it, this is how you "skim" the code efficiently: you figure out where the Instruction Pointer might go, and then you follow it through the blocks of code it might execute along the way.
In the absence of macros, this is possible. But in the presence of macros—any of which could slice-and-dice your code, rearranging it, throwing some bits away, duplicating others, wrapping any given given expression in an if{} or a while{} or a lambda{} or a synchronize{}—you now have no idea what a given block of code that calls a macro will do. Where is the Instruction Pointer allowed to go? Who knows? It might even enter—for quite a long time—code that's only referred to in the macro body, not at the macro call-site. (But only sometimes; at other call-sites the macro-expansion might not call that function at all. It might depend on the function. It might depend on the module attributes. It might depend on the time of day!)
A block of code passed through a macro is its own world with its own rules. Those rules might be 100% similar to our own world save for one little thing... or they might be "only compile this to an expression if DEBUG has been defined at the project level" or "apply the statement body to itself as a Y-combinator" or "convert all blocking calls in your function into async calls followed by returns to the trampoline of an FSM to enable continuations, splitting your block at those points into several private functions."
You have to understand the macros. Either their documentation, or their code if that is lacking. If we don't know the macro at all, we can't do static or dynamic or any kind of analysis; we don't have the information.
Do you know what happens inside a function without chasing its definition, and then chasing its children and so on?
Programming is all about "controlling something with this little piece of text here, such that the real meat of it is done in multiple elsewheres". You do this with functions, or macros. If you don't do this ("everything happens right here and nowhere else") you will not get anywhere beyond a certain small complexity.
Okay, I know that a function in C is call by value. These expressions are evaluated and converted into argument values. They are not relocated into another scope, or turned into thunks or whatever. Well, so what? Once the function takes over, I have no idea, if I don't know what the function does.
Yes. That was my point: when you know a language, you can "unfocus your eyes" and read code without knowing anything about what the code does, except that A calls B calls C. You don't need to know what A/B/C are, or what they're supposed to do; if the program crashed, "errors in business logic" aren't your concern. You want to find an error in the model of the world the program is using, that lead to an "impossible" scenario. You want to uncover a series of static guarantees—or lack thereof—that lead to the possibility of a code-path [containing a throw() or an abort() or whatever] being activated that is never supposed to be activated.
If you have a crash with a backtrace of function C <- function B <- function A, then you will start with A, and look at the code around the call to B to see if any of it "is relevant"—i.e. if it might, lexically, have been the cause of the problem—and, after a second or two, you likely determine that it wasn't. So you move on to B. Do the same. Move on to C. Stare at the code. See something that smells vaguely wrong. Then start trying to understand the code, from there. Switch from skimming, to reading.
Any developer versed in a language can do this to any program written in that language, with no idea what it is they're reading. This is why "over-the-shoulder" debugging can magically work—if you know a language, you can "know broken code when you see it" without knowing a thing about the code itself.
But if, in the above, function A's call to function B, or B's call to function C, happened within a block wrapped in a macro call—now you can't skim. Now Mr. Joe "over-the-shoulder" Schmoe has no idea whether A or B are relevant. Now you have to go and find the docs/definitions of those macros, and familiarize yourself with them, before you can do anything.
If there's one macro, this is easy enough. FOSS projects of this type still have many contributors, although there might be sections of the codebase considered "harder to contribute to" than others. (See: the Linux kernel's syscall mapper macros.)
But if the whole language is effectively half-DSL—and not a business domain kind of DSL, but just the developers' idea of a bunch of awesome extra primitives, like Arc or Elisp—then random drive-by "oh, hey, I noticed that was broken and fixed it" FOSS contributions will go to zero, because nobody will spend enough time getting versed in the new one-off language to be able to "notice something was broken."
But at least inside the Linux kernel everyone is talking the equivalent of English.
Understanding English does not imply you will be able to jump into any random scientific paper with 45 years of background theory and immediately understand it either.
Being able to understand the grammar and sentence structure really helps, even if you don't understand the vocabulary or technical principles of the domain you are reading.
A macro is just another function, but it does some code transformation. The code base will not explode, the world will not stop revolving, just because a few macros.
I use LispWorks, which adds a few hundred macros (amongst many other things) to Common Lisp. It's still Common Lisp, but extended with macros for defining new data types, macros for defining UI features, even a few new control structures have been added. For most of the stuff I don't even have the source code, since it is a closed commercial product - but the macros are documented.
Lisp is still used and one of the reasons is its seamless integration of various meta-programming facilities. This requires developers to learn a slightly more complicated language. But in larger projects this is not more difficult than learning the architecture tools of large OO frameworks - frameworks which are common in Java, Javascript and many other languages.
Have you conducted a survey which confirms that lots of people want to help, but for the macros? Or are you guessing at the reasons?
First of all, do lots of people use it; is there a large user to contributor ratio?
It's hard to get help with a FOSS project no matter what; the ones that get help are vastly outnumbered by those which don't get help. According to openhub.net, "over half of all active projects [listed] on Open Hub are solo efforts". (This is boiler plate text which appears in the summary of projects which are that way.)
I don't think anybody will disagree.
Sounds about right.
I dearly love C, but it's time to move on. Languages like Rust seem to solve most of the major pitfalls while not sacrificing on speed.
If it is the former free to use whatever language you choose. If it is the latter, can you please keep your opinion as just that.
I believe we are all adults here and quite capable of making judgment calls.
Rust has some interesting features and lots of other languages too. No need for the drive by.
I'm not sure how much more diplomatically I could have phrased my previous post. If mentioning a language as one possible modern alternative ("languages like Rust") is enough to set you off, perhaps you need to reconsider who's falling short of adulthood here.
What you said is it is about time we moved on from C and suggested Rust.
That seems like your own subjective opinion. Which is fine of course, each to their own.
Personally I will continue to use whatever language makes sense for the job no matter what it might be.
Happy to take one for the team if that stance doesn't work for you, I'm not the one here passively aggressively proselytizing.
And thank you for confirming that by the way.