You want the optimization post not the C language post, I think
You want the optimization post not the C language post, I think
For each object of such a composed type, there was already a way to mention the underlying object: index the array, call the function, use the indirection operator on the pointer. Analogical reasoning led to a declaration syntax for names mirroring that of the expression syntax in which the names typically appear. Thus,
int i, *pi, **ppi;
declare an integer, a pointer to an integer, a pointer to a pointer to an integer. The syntax of these declarations reflects the observation that i, *pi, and **ppi all yield an int type when used in an expression. Similarly,
int f(), *f(), (*f)();
declare a function returning an integer, a function returning a pointer to an integer, a pointer to a function returning an integer;
After almost 30 years, I think I just understood function pointer declaration syntax for the first time.
I thought I was the only one… to me unless it’s a super-simplistic comprehension, it has a similar effect as when C programmers write if (xx = !(1 == (a ? !c : 34 ^ blit_target))) {
. Congratulations, you fit it all on one line! At the expense of totally destroying my train of thought when I’m trying to scan down the code and figure out what the hell’s going on. Well done.
Hmmm
I worked on one project only which used what I guess is an ORM-like pattern, and I have to say it was actually really nice. The code was Javascript, and there was a mapping:
For each class, there was a big mapping table which indicated which database-backed fields needed to exist in that class, and then there was automated code that (1) could create or update the database to match the specified schema (2) would create helper methods in each class for basic data functions – the options being “Create me a new non-database-backed object X” “I’ve set up the new object, insert it into the DB” “give me an iterator of all database-backed objects matching this arbitrary query”, “update the appropriate row with the changes I’ve made to this object”, “delete this object from the DB,” and “I’m doing something unusual, just run this SQL query”.
I honestly really liked it, it made things smooth. Maybe it was the lack of hesitation about dropping back to SQL for stuff where you needed SQL, but I never had issues with it and it seemed to me like it made life pretty straightforward.
Yah, 100% agree. So in my case, Typescript is actually a better fit niche-wise… but I finally reached the point where I didn’t want to cope anymore with flawed things it was inheriting from Javascript, so it was especially ironic to me when I saw this and thought maybe I’m not the only one, only to discover that the message was “SIIIKE we love the flawed things! It’s the improvements we’re getting rid of.”
I mean every project is different and they’ve got a right to do what they want. It was just a hilarious surprise for me.
I clicked, sorta interested in the topic because as it happens I just abandoned Typescript in favor of learning Go for a little nascent project I’m working on. (We do these things not because they are easy, but because they eventually will be good which Typescript+Node apparently never will for my particular project.) Then I saw it was Fireship and clicked back away.
Now it sounds like you’re saying that what they’re claiming is the issue is the one good thing that happened with Javascript in the last however-many years. Yes, it’s a little tedious to have to straighten out all your types. You know what’s more tedious than that? Having to straighten out all your types, but not getting any feedback about it at compile time and having to figure it out based on mysterious failures, or if you’re lucky explicit exceptions, at runtime.
Having worked for a while in Javascript, and now for a pretty short time in Typescript, I cannot imagine trying to make a decent-complexity project in pure Javascript. That I believe is why they tend to not really be all that object-oriented and pass strings around for lots of things and be sorta flaky.
In my opinion, C is a difficult starter language, but one that sets you up for very good abilities in the future, because a lot of other languages’ core concepts are built on C concepts. It’s all bytes, and if you’re comfortable with bytes then that’ll put you ahead of someone who only knows the general concepts. It’s similar to working construction before you go into architecture – your buildings will be better even if you’re thinking at a higher level than nails and shingles when you do most of your design.
So, ChatGPT is tuned to sort of do things for you, but you can still have it do the troubleshooting with the right prompting instead of just rewriting:
GPT-4 Prompt
I’m trying to understand what’s going wrong with my code – can you point me to the line that’s causing the issue and quickly explain what might be wrong?
import random
word_list = ["aardvark", "baboon", "camel"]
chosen_word = random.choice(word_list)
#Testing code
print(f'Pssst, the solution is {chosen_word}.')
#Create an empty List called display.
#For each letter in the chosen_word, add a "_" to 'display'.
#So if the chosen_word was "apple", display should be ["_", "_", "_", "_", "_"] with 5 "_" representing each letter to guess.
display = ["_"] * len(chosen_word)
guess = input("Guess a letter: ").lower()
#If the letter at that position matches 'guess' then reveal that letter in the display at that position.
#e.g. If the user guessed "p" and the chosen word was "apple", then display should be ["_", "p", "p", "_", "_"].
for letter in chosen_word:
if guess == letter:
for i in range(len(chosen_word)):
display.insert(i, guess)
print(display)
GPT-4’s response
The problem with your code is in this segment:
for letter in chosen_word:
if guess == letter:
for i in range(len(chosen_word)):
display.insert(i, guess)
The logic you’re using here is:
This will make the display list longer than it should be, and the letters will not replace the underscores but instead will be added alongside them.
Instead, you should check if the guessed letter matches a letter in the chosen word at a specific position, and if it does, replace the underscore at that position in the display list.
Here’s the corrected segment of code:
for i in range(len(chosen_word)):
if chosen_word[i] == guess:
display[i] = guess
With this, the guessed letter will replace the corresponding underscore in the display list.
So - ChatGPT is great for breaking down concepts like this and answering questions about the basics until you get the hang of it. I would recommend crafting some programs while asking it for help on anything you get stuck with, until you can craft the programs without involving it (and still you can paste the programs into it and ask for pointers / fixes that it can see). I’m currently learning Go with assistance from ChatGPT and it’s hugely useful.
One other angle you could come at it from – this might venture into unpopularopinion territory, but I would actually recommend learning C at a very early stage. It’ll be tedious to make nontrivial programs with it, so you may not want to stick with it for your real projects, but since everything is bytes and pointers it gives you a chance to get extremely solid with the fundamentals before you start mucking around with slices / hashes / etc. I would recommend to try to get this particular problem working using C, which will be more work, but then when you come back and learn the Python concepts I think they’ll make a lot of sense and you’ll feel capable with them. IDK if it’ll work that way for you, but that’s what I did and I feel like it was a good way to go.
Best of luck + keep plugging
I would use python instead of BASIC, if it was me. I also started with BASIC as a kid, but I remember each step up language wise (BASIC -> Pascal -> C) being a big satisfaction of “hey, it seems like this language is a lot better and I can do more with it.” I would echo the recommendation to use actual code though. Language is pretty deeply hard-wired into human beings, and I suspect that the kids that will do well with breaking tasks down into scratch primitives would do equally well with python, and the kids who find python “too hard” or something would also not be able to do too much with scratch. Maybe I am wrong, but that’s my guess.
My only other thought is to have some kind of graphical / game you can play / real world robotics angle to it. Maybe there’s a little graphical ecosystem pre-provided, and they can write agents that can interact within the ecosystem and then see a visual representation of what everyone’s agents are doing. I would definitely recommend to have a bunch of code that they can read, though; that was where my programming as a kid took a big step forward, was when I got a big disk filled with programs I could analyze and break down.
He’s not completely, though. @marcos had it right about co-evolution – leaving aside any issues of internationalization, the layout of letters came from typewriters, but the layout of punctuation available was different on different computers for a lot of the early history of programming. Some of the more extreme examples were the Space Cadet Keyboard used at MIT, and APL which more or less required you to use an APL-specific keyboard in order to be able to access all the special symbols that are part of APL. Here’s an APL program:
⎕CR 'ProveNonPrime'
Z←ProveNonPrime R
⍝Show all factors of an integer R - except 1 and the number itself,
⍝ i.e., prove Non-Prime. String 'prime' is returned for a Prime integer.
Z←(0=(⍳R)|R)/⍳R ⍝ Determine all factors for integer R, store into Z
Z←(~(Z∊1,R))/Z ⍝ Delete 1 and the number as factors for the number from Z.
→(0=⍴Z)/ProveNonPrimeIsPrime ⍝ If result has zero shape, it has no other factors and is therefore prime
Z←R,(⊂" factors(except 1) "),(⊂Z),⎕TCNL ⍝ Show the number R, its factors(except 1,itself), and a new line char
→0 ⍝ Done with function if non-prime
ProveNonPrimeIsPrime: Z←R,(⊂" prime"),⎕TCNL ⍝ function branches here if number was prime
Things became much more standardized when the IBM PC’s keyboard became the norm, and were formalized in 1995 with ISO 9995. Then once it stabilized there was a strong incentive for both language designers and keyboard makers to stick with what everyone was used to so they could keep working with the other. But it wasn’t always that way.
Edit: Here’s what things looked like on an IBM 3276:
Why is this even still in the library 🥲
Twenty years ago it kind of made sense. Ok it’s bad, but sometimes we’re just reading a local file fully under our control, maybe from old code that the source doesn’t exist anymore for, it’s such a core function that taking it out however badly needed will have some negative consequences.
At this point though, I feel like calling it should just play a loud, stern “NO!” over your speakers and exit the program.
The dev’s explanation, in full, is:
The precompiled implementation is the only supported way to use the macros that are published in serde_derive.
If there is implementation work needed in some build tools to accommodate it, someone should feel free to do that work (as I have done for Buck and Bazel, which are tools I use and contribute significantly to) or publish your own fork of the source code under a different name.
Separately, regarding the commentary above about security, the best path forward would be for one of the people who cares about this to invest in a Cargo or crates.io RFC around first-class precompiled macros so that there is an approach that would suit your preferences; serde_derive would adopt that when available.
Not “Here’s why I’m doing this, it might seem weird but there’s a good reason” or anything. Just, go fuck yourself, run my binary.
I smell a similar resolution to the xfree86 -> xorg transition, where the community unanimously abandons serde in favor of the fork.
It’s a symptom of a general lack of respect and lack of desire to collaborate with one another. Compared to real problems you’ll face in trying to communicate and collaborate towards a goal, it’s a miniscule level of effort required to get on the same page with what things are called and then roughly stick to it going forward.
Is the difference in naming going to make a difference? Maybe. The little extra bit of cognitive load and confusion that can result may or may not hurt, although it definitely won’t help. How about the lack of shared desire to put in the effort that it takes to operate as a cohesive unit; is that going to make a difference? Yes, yes, a thousand times yes.
It may or may not be something worth stressing over or trying to fix on your end (sometimes it just be that way), but it’s definitely not unimportant.
For example, you can assign a float* p0 to a size_t i, then i to a float* p1 and expect that p0 == p1. Here the compiler is free to choose how to calculate i, but other than that the compiler’s behavior is predictable.
I don’t think this specific example is true, but I get the broader point. Actually, “implementation defined” is maybe a better term for this class of “undefined in the language spec but still reliable” behavior, yes.
“Undefined behavior” is not “machine-dependent” code
In C, that’s exactly what it is (or rather, there is some undefined-in-the-spec behavior which is machine dependent). I feel like I keep just repeating myself – dereferencing 0 is one of those things, overflowing an int is one of those things. It can’t be in the C language spec because it’s machine-dependent, but it’s also not “undefined” in the sense you’re talking about (“clever” programming by relying on something outside the spec that’s not really official or formally reliable.) The behavior you get is defined, in the manual for your OS or processor, and perfectly consistent and reliable.
I’m taking the linked author at his word that these things are termed as “undefined” in the language spec. If what you’re saying is that they should be called “implementation defined” and “undefined” should mean something else, that makes 100% sense to me and I can get behind it.
The linked author seems to think that because those things exist (whatever we call them), C is flawed. I’m not sure what solution he would propose other than doing away with the whole concept of code that compiles down close to the bare metal… in which case what kernel does he want to switch to for his personal machine?
Well… I partially agree with you. The final step in the failure-chain was the optimizer assuming that dereferencing NULL would have blown up the program, but (1) that honestly seems like a pretty defensible choice, since it’s accurate 99.999% of the time (2) that’s nothing to do with the language design. It’s just an optimizer bug. It’s in that same category as C code that’s mucks around with its own stack, or single-threaded code that has to have stuff marked volatile
because of crazy pointer interactions; you just find complex problems sometimes when your language starts getting too close to machine code.
I guess where I disagree is that I don’t think a NULL pointer dereference is undefined. In the spec, it is. In a running program, I think it’s fair to say it should dereference 0. Like e.g. I think it’s safe for an implementation of assert() to do that to abort the program, and I would be unhappy if a compiler maker said “well the behavior’s undefined, so it’s okay if the program just keeps going even though you dereferenced NULL to abort it.”
The broader assertion that C is a badly-designed language because it has these important things undefined, I would disagree with; I think there needs to be a category of “not nailed down in the spec because it’s machine-dependent,” and any effort to make those things defined machine-independently would mean C wouldn’t fulfill the role it’s supposed to fulfill as a language.
I could talk for a long time about things I don’t like about C++. This type of stuff doesn’t even scratch the surface lol.
Years and years ago I actually wrote up a pretty in-depth blog post talking about it, even going so far as to show that it’s not even faster than the competitors once you’ve added in all this overbloated garbage that it calls a standard library. I wrote up a little toy implementation of some problem in C, Python, C++, and a couple other languages, and lo and behold then C one was faster by a mile and the C++ one using all the easier C++ abstractions was pretty comparable with the others and actually slower than the Perl implementation.
Right, exactly. If you’re using C in this day and age, that means you want to be one step above assembly language. Saying C should attempt to emulate a particular specific architecture – for operations as basic as signed integer add and subtract – if you’re on some weird other architecture, is counter to the whole point. From the point of view of the standard, the behavior is “undefined,” but from the point of view of the programmer it’s very defined; it means whatever those operations are in reality on my current architecture.
That example of the NULL pointer use in the kernel was pretty fascinating. I’d say that’s another exact example of the same thing: Russ Cox apparently wants the behavior to be “defined” by the standard, but that’s just not how C works or should work. The behavior is defined; the behavior is whatever the processor does when you read memory from address 0. Trying to say it should be something else just means you’re wanting to use a language other than C – which again is fine, but for writing a kernel, I think you’re going to have a hard time saying that the language need to introduce an extra layer of semantics between the code author and the CPU.
I’m definitely open to the idea that C and C++ have problems, but the things listed in this article aren’t them. He lists some very weird behavior by the clang compiler, and then blames it on C despite the fact that in my mind they’re clearly misfeatures of clang. He talks about uncertainty of arithmetic overflow… unless I’ve missed something, every chip architecture that 99% of programmers will ever encounter uses two’s complement, so the undefined behavior he talks about is in practice defined.
He says:
But not all systems where C and C++ run have hardware memory protection. For example, I wrote my first C and C++ programs using Turbo C on an MS-DOS system. Reading or writing a null pointer did not cause any kind of fault: the program just touched the memory at location zero and kept running. The correctness of my code improved dramatically when I moved to a Unix system that made those programs crash at the moment of the mistake. Because the behavior is non-portable, though, dereferencing a null pointer is undefined behavior.
This is the same thing. He’s taking something that’s been a non-issue in practice for decades and deciding it’s an issue again. Yes, programming in C has some huge and unnecessary difficulties on non-memory-protected systems. The next time I’m working on that MS-DOS project, I’ll be sure to do it in Python to avoid those difficulties. OH WAIT
Etc etc. C++ actually has enough big flaws to fill an essay ten times this long about things that cause active pain to working programmers every day… but no, we’re unhappy that arithmetic overflow depends on the machine’s reliably-predictable behavior, instead of being written into the C standard regardless overriding the machine architecture. It just seems like a very weird and esoteric list of things to complain about.
Edit: Actually, I thought about it, and I don’t think clang’s behavior is wrong in the examples he cites. Basically, you’re using an uninitialized variable, and choosing to use compiler settings which make that legal, and the compiler is saying “Okay, you didn’t give me a value for this variable, so I’m just going to pick one that’s convenient for me and do my optimizations according to the value I picked.” Is that the best thing for it to do? Maybe not; it certainly violates the principle of least surprise. But, it’s hard for me to say it’s the compiler’s fault that you constructed a program that does something surprising when uninitialized variables you’re using happen to have certain values.
If you met or worked with the people who post the majority of the programming-language-ism posts, you would know deep down in your bones how little you need to be listening to them.