Tangent, but why is it inaccurate that C is described as "portable assembler" ?
That's a bit of an exaggeration, at worst -- yes, C is a bit more high-level than a typical assembly language, but most C constructs can be mapped to a typical processor's assembly language in an obvious way, something that isn't true of any other language I know of.
Maybe because by the time the compiler is finished optimizing the code it doesn't match the source code anymore line by line, but only in the intended effect of execution.
Right. There might be an obvious mapping for any C construct, but there's absolutely no guarantee that a C compiler will do the obvious thing. The C standard leaves much behavior undefined (wolfgke listed some of the more notorious items), and modern compilers exploit that. People who view C as "portable assembler" end up being very disappointed.
> There might be an obvious mapping for any C construct
I don't know much about the PDP-11, the machine for which C was originally designed, but for modern processors this "obvious" mapping is typically not how things are done in assembly. To give an example:
int x = 42;
x = x+1;
foo(&x);
The way how it was originally conceived in C was that we reserve, say, 4 bytes on the stack and pass the adress of x on the stack to foo. On the other hand, any assembler programmer would try to store integer variables in a processor register (where we can't take an adress) and synchronize it with the x on the stack before we pass x's adress to foo. In other words: x now is now "two variables": 4 bytes on the stack and a register, which are synced if necessary.
Summarizing: In assembly we have "two kinds of local variables":
- Registers [one cannot take adress of it]
- Adresses in stack frame [on RISC cores (load-store architecture) these can only be accesses by an explicit load/store and must be loaded into register before anything useful can be done with them; on x86-32/x86-64 the story is a little more complicated - I don't want to go into the details here]
As I have shown these two completely different concepts are intertwined in C into the conept of local variables. I hope this convinces you how the concepts in C don't map directly to modern assembly code (i.e. a direct mapping yields bad assembly code).
Ironically C has the concept of "register" variables, of which one cannot take the adress. This feature is hardly used in modern C, but maps IMHO much better to the "register" concept in assembly (but I'm not a C expert).
> Tangent, but why is it inaccurate that C is described as "portable assembler" ?
- Strict aliasing rules (most important reason why C should not be considered as "portable assembler")
- Overflow for adding/subtracting signed numbers is undefined behavior
- Implicit casting of integer types in expressions; OK, there are rules for this, but under assembly you have all this under direct control without having to learn lots of arcane rules
- There are things that can easily be done in assmbly that cannot/can hardly be done in C (for example taking adress of a label (very useful for implementing coroutines) is only an GCC extension but not standard C: http://stackoverflow.com/q/1777990/497193). Or how do you control the amount of stack space that a function is allowed to use (would be very useful for embedded applications where you strictly want to avoid stack overflow and you want to allocate exactly as much stack space as necessary, but not more)
C was very close to the metal in the earliest versions (PDP-11).
Since then, CPU architectures evolved and even though the language evolved too, C compilers somehow have more work to do to map the programmer's intent to the actual hardware.
Any C programmer working at large scale will need to rely on third-party libraries. That library code is an abstraction.
When I write C++ the language magic is "just another library". If I'm uncomfortable I just step through those magic layers in a debugger and I'm then happy, just like a C library user would step through compiled-from-source third party stuff.
"But a C coder can read the third-party code!" But a C program already hides the magic of function calls and stack frames ... C++ is just a few more layers of abstractions just like that one!
> When I write C++ the language magic is "just another library".
The difference is: For a library you can (in principle) write your own implementation and replace the given one by it. Or even remove the library completely if it seems a good idea. For language features this is not possible (without writing a new compiler that implements something different than C/C++). Thus the language features of C++ clearly cannot be compared to a library - they change the language (if left away), while a library is no different language, but a text written in the language.
> But a C program already hides the magic of function calls and stack frames ... C++ is just a few more layers of abstractions just like that one!
In principle yes, but there are good and bad abstractions (bitshifts that print probably being in the latter category), and facilities that break important symmetries of their surrounding code (exceptions).
Exactly, I mean, which language designer would reuse the the very common stream input/output operators for such an uncommon bit manipulation operation!
Slightly less tongue-in-cheek, what sort of symmetries are broken by exceptions?
> Slightly less tongue-in-cheek, what sort of symmetries are broken by exceptions?
They make it much harder to maintain single entry/exit in functions where you want that - and, crucially, harder to notice when a function isn't single entry/exit. They also violate referential transparency, which means e.g. caching the result of a repeated function call is no longer necessarily safe.
> caching the result of a repeated function call is no longer necessarily safe
Not seeing this one. Unpredictable exception-throwing only happens if the work being done by your function isn't pure, i.e. it depends on something other than the function parameters. And if that's the case then caching its result is a bad idea anyway.
If you make an exception-aware cache that part will behave correctly. The problem is that you can't just move the call around, store the value and use it later, because you're changing where the evaluation happens, so that can lead to an exception happening earlier or later and changing which functions get evaluated. "Everyone knows" you shouldn't use exceptions as control flow, but it's very easy to not realise you were doing it by accident (I once came across a method that always exceptioned and never did what it looked like it was meant to) until you refactor and break something.
> Unpredictable exception-throwing only happens if the work being done by your function isn't pure, i.e. it depends on something other than the function parameters.
This isn't true. The canonical example is trying to grab the first element of an empty list. You either check and catch the error or eat the exceptional behavior, but some pure functions are just partial and aren't defined for all inputs.
(Whether or not they throw a catch-able exception depends on language/implementation and is really not relevant to my point.)
It's unpredictable in the sense that even with a compile-time check that the function I'm calling is pure, I don't have any guarantee that it won't throw on my particular inputs.
This comes up a lot in the Haskell community -- code outside IO is guaranteed by the compiler to be pure, but you can still get hit with an asynchronous exception because you called a partial function.
> Too obscure?? it is literally the first thing one learn in C++!
And that is really puzzling me. Why do tutorials (from 20 years ago) starts with "cout << hello world;" ???
> Exceptions have been quite well supported by all major compilers on all major platforms for a very long time.
Try throwing exceptions in the kernel or in a driver, see how well it goes, or try in an embedded device. (That is, when it can be done in C++, and not C only).
In my experience, the C++ programmers I've worked with did tend to have a bit of an obsession with abstraction. They're far more likely to build complex systems to solve simple problems. I suspect it's a combination of OOP hype (not as prevalent today, but still noticeable) and all the "magic" features that attract such programmers, as it gives them enjoyment to work with complex things.
On the other hand, C doesn't seem to attract the same type of programmers because the language itself is relatively spartan. I have a theory that simpler languages naturally encourage programmers to find simpler solutions both for themselves and the machine (thus possibly being more efficient), because more complex ones would involve more work. Asm is an extreme example, where you have to basically write every instruction the machine will execute --- and in such a scenario, even if it's mostly library calls, you're not going to want to construct and destruct a dozen objects unless you absolutely, unconditionally need to.
I feel this is a criticism of the past. In the last decade a lot of places were abstraction was required (threads, etc), we've seen good libraries appear. This has greatly simplified code bases, especially if you measure by lines of code.
That would bring out of the woodwork all the people who disagree that the lisps and schemes have a common syntax as well as all the people who disagree that lisps have a syntax at all.
As a C programmer, I think I find C++'s dizzying abstractions scary because C, and thus C++, is an unsafe language.
In C, I have to maintain careful discipline in memory management. In C++, the same operators used for this in C will now sometimes do this for you, except when they don't. And so I look at C++ code and I have no idea what it's doing, and I don't know if it's safe.
In Python or whatever I needn't worry because there aren't any actual pointers or heap corruptions or segfaults.
Mind you, I have very little experience writing C++ code, so maybe I'd get used to it.
I think nowadays it's a bit of a misconception that C++ is unsafe regarding memory; RAII essentially takes care of it for you when you talking about IO or other resources, and under the circumstances that you really need a pointer you can either use a reference or a smart pointer, which essentially just delegates the management of RAII to the smart pointer object.
Maybe a better metaphor would be C is a car with a knife pointing out of the steering wheel. The fact that it's dangerous makes the person drive safer and more cautiously.
Nah... C is just a motorcycle. Primitive, zippy, dangerous, loud, and fun. People who don't ride don't understand; people who do ride are OK with the risks they're taking.
It's a often repeated idea that the dangers of making mistakes with C code will scare the programmers into writing correct code. This completely fails in my experience. I've worked on large C projects that suffered a ton of leaks, free-after-use, buffer overruns, etc.
RAII is great until you stumble upon an API that is terrible and doesn't care. If you can't use exceptions or have any limitations that make RAII difficult to use, then suddenly everything becomes a lot messier.
Alternatively you can take it the other route and discard RAII but then you're basically just writing C.
Some of the most heinous, insane bugs I have ever seen are because C++ is a complexity nightmare and very intelligent people struggle to get things right. Rust is orders of magnitude safer as it forces you to play with safe constructs only (avoiding surprises left by others, or past you) and wrapping anything explicitly unsafe.
Even smart pointers can trivially cause memory unsafety. The simplest example is that std::move-ing a uniq_ptr makes the original nullptr, which is just sitting around, waiting to be dereferenced. This compiles cleanly with all warnings enabled on every compiler I've tried it on, yet that dereference is UB.
While conventional C++ is a vast improvement in memory safety, it does not, at the moment, fully address the problem. Complete memory safety may be achieved if/when the "Core Guidelines Lifetime Checker"[1] is completed, but in the mean time SaferCPlusPlus[2] can be used to achieve memory safety. SaferCPlusPlus can, of course, be used with C code as well (if you're willing to use a C++ compiler), but will typically be slower than with the corresponding C++ code.
I know HN gets a lot of crap for having a ton of Go fanboys but the reasons given here for liking C are the reasons I am in love with Go. No magic, no overloading, very clear when I do this this happens, and very easy to read code including in the standard library. It's a more friendly C.
I just set the $GOPATH per project and it stops being a big problem, and turns into just an inconvenience.
If you have a ProjectX with the path $pathx, set $GOTPATH to $pathx and place your source files in $pathx/src/projectx
I also hate that I have to do this though and I totally understand what you mean. Just wanted to mention my favorite work-around to see if I get some feedback on it being stupid/clever/ordinary.
It's not really any different to supplying the source paths in C / C++ build scripts. Or setting the Java variables in your preferred IDE. At the of the day the compiler isn't going to know where to import external source files from if you don't specify it somewhere. The GOPATH envvar just shortcuts the need to use build flags but if you want to do away with system wide Go variables then you can always write build scripts more akin to those we see in some other languages.
Allows me to change and modify on a per project basis how to compile a program. In fact, it allows me to change exactly how it's compiled or cross-compiled.
I can use system libs if available, and if not, drag down the latest, build that locally, and link locally.
Go isn't as flexible. It's opinionated.
There doesn't seem to be any benefit other than standards compliance in having a GOPATH.
I have to circumvent how Go wants to work so that it can fit with the standards I have that C, C++, C#, F#, Haskell, D, Nim, Red, Chicken, Python all work with without being bashed into submission, they supply tools to make it easier.
They don't have lead developers standing up and saying "All Python libraries should be developed in this particular folder structure. Even if it makes integrating C code more difficult."
That feels... Wrong.
So repeatedly setting GOPATH sucks, and can cause havoc if the envar isn't released properly by, say, a failing build.
But, as I said to start with: This is a tiny knitpick.
It means I come back and try Go, time and again.
It also means I spend more time wrestling Go into submission within my automated testing and deployment structures, than writing code in it.
But again, you can do that with Go very easily. eg the -pkgdir flag in `go build`.
You can also write your own wrapper script around Go build tools. Or even bypass `go build` entirely and call the Go compiler in the exact same way you'd described above (-o and -L flags included). Run `go build -n` against your project to see what `go build` does (it's print only mode, no compiling will happen) and you'll see it's really no different to what you were asking for
So everything you want to do can and is supported by Go. It's just not it's default behaviour so you needed to do a little digging to find it.
Personally though, I find the inconvenience of having to change GOPATH / GOOS / GOARCH to be significantly less annoying than having to write makefiles and configure scripts to handle every platform I might want to target. For most people `go build` abstracts away the pain of cross compiling and build scripts quite nicely. In fact that was one of the biggest reasons I started using Go in the first place (I wanted something I could rapidly code and compile on AMD64 then rsync onto ARM devices back when I was experimenting with building custom embedded devices for my car).
edit: just want to add that I'm not under any illusion that Go nor it's tooling is perfect. I'm just saying Go can easily support your specific workflow preferences even though superficially it appears otherwise.
If you don't mind me saying, maybe some of the hostility is because you keep saying Go "cannot" do x, y or z without looking at said documentation first? I know I've gotten terse responses from the community in the past when I've asked questions that were already well explained in the docs. I agree it's not nice being on the receiving end of such responses but I can also sympathise with why the community get sick of answering basic questions that are already available in a simple Google / DuckDuckGo search.
I don't really know how to respond because I found the answers you wanted by looking briefly in the docs.
> But, for example, the documentation for GOPATH makes no suggestion it is even possible [1]
Well to be fair you were looking for information about compiler flags in the documentation for the envvars; which perhaps wasn't the best systematic approach to research your problem? But hopefully you now have the information you need? Or at least better idea of how to find the answers you seek?
>The team want one true way of building a go pkg.
This is very true. However as long as your code follows the same standards it doesn't really matter how you chose to organise your source files as it should still compile on other people's systems using `go build` in the idiomatic way. And for what it's worth, a lot of people don't follow the directory hierarchy in the strictest way and/or use `go build/install`; choosing instead to use their preferred vendoring system. So I doubt many people would have a problem if you wanted to do your own thing just so long as the code is still compatible with Go's standard build tools should you release your projects to the world.
I can see why Go might become annoying with that kind of file system hierarchy but I think it's an easily solvable problem. Most IDE's I've used allow you to set custom $GOPATH variables and for the CLI stuff you could write a wrapper makefile or similar.
I understand that different people find themselves preferring different programming languages based on their syntax, features, tooling, etc. But I do think it would be a shame not to use a language you conceptually like just because of a single and easily changeable environmental variable.
I have/had this issue too. While I'm just exploring go, it looks like $GOPATH can be split into multiple directories. So you could just update it to have a new project. Still weird coming from a Maven/Java perspective, but more reasonable than having to have a script that alters the shell in order to change projects.
> A C++ programmer, meanwhile, comes at things from a different perspective: code written by humans is buggy; [1] instead, as much as possible of the complexity should be shuffled to the compiler.
As though compilers aren't themselves written by humans.
When abstractions move into the language itself, it's harder to observe what's going on when they do something strange (even if it's correct) than if they were just some other library code in the same program.
I generally agree with the article's main point, however c still has macros which kind of breaks those rules.
I remember back in 98 when first picking up Java, thinking, WHAT NO MACROS?, but now i believe that was an incredibly good idea not having them. Of course Java now has things like aspects, et. al, so you never can get away from things if you really want them.
56 comments
[ 3.3 ms ] story [ 143 ms ] threadThat's a bit of an exaggeration, at worst -- yes, C is a bit more high-level than a typical assembly language, but most C constructs can be mapped to a typical processor's assembly language in an obvious way, something that isn't true of any other language I know of.
I don't know much about the PDP-11, the machine for which C was originally designed, but for modern processors this "obvious" mapping is typically not how things are done in assembly. To give an example:
The way how it was originally conceived in C was that we reserve, say, 4 bytes on the stack and pass the adress of x on the stack to foo. On the other hand, any assembler programmer would try to store integer variables in a processor register (where we can't take an adress) and synchronize it with the x on the stack before we pass x's adress to foo. In other words: x now is now "two variables": 4 bytes on the stack and a register, which are synced if necessary.Summarizing: In assembly we have "two kinds of local variables":
- Registers [one cannot take adress of it]
- Adresses in stack frame [on RISC cores (load-store architecture) these can only be accesses by an explicit load/store and must be loaded into register before anything useful can be done with them; on x86-32/x86-64 the story is a little more complicated - I don't want to go into the details here]
As I have shown these two completely different concepts are intertwined in C into the conept of local variables. I hope this convinces you how the concepts in C don't map directly to modern assembly code (i.e. a direct mapping yields bad assembly code).
Ironically C has the concept of "register" variables, of which one cannot take the adress. This feature is hardly used in modern C, but maps IMHO much better to the "register" concept in assembly (but I'm not a C expert).
- Strict aliasing rules (most important reason why C should not be considered as "portable assembler")
- Overflow for adding/subtracting signed numbers is undefined behavior
- Implicit casting of integer types in expressions; OK, there are rules for this, but under assembly you have all this under direct control without having to learn lots of arcane rules
- There are things that can easily be done in assmbly that cannot/can hardly be done in C (for example taking adress of a label (very useful for implementing coroutines) is only an GCC extension but not standard C: http://stackoverflow.com/q/1777990/497193). Or how do you control the amount of stack space that a function is allowed to use (would be very useful for embedded applications where you strictly want to avoid stack overflow and you want to allocate exactly as much stack space as necessary, but not more)
When I write C++ the language magic is "just another library". If I'm uncomfortable I just step through those magic layers in a debugger and I'm then happy, just like a C library user would step through compiled-from-source third party stuff.
"But a C coder can read the third-party code!" But a C program already hides the magic of function calls and stack frames ... C++ is just a few more layers of abstractions just like that one!
The difference is: For a library you can (in principle) write your own implementation and replace the given one by it. Or even remove the library completely if it seems a good idea. For language features this is not possible (without writing a new compiler that implements something different than C/C++). Thus the language features of C++ clearly cannot be compared to a library - they change the language (if left away), while a library is no different language, but a text written in the language.
In principle yes, but there are good and bad abstractions (bitshifts that print probably being in the latter category), and facilities that break important symmetries of their surrounding code (exceptions).
Slightly less tongue-in-cheek, what sort of symmetries are broken by exceptions?
They make it much harder to maintain single entry/exit in functions where you want that - and, crucially, harder to notice when a function isn't single entry/exit. They also violate referential transparency, which means e.g. caching the result of a repeated function call is no longer necessarily safe.
Not seeing this one. Unpredictable exception-throwing only happens if the work being done by your function isn't pure, i.e. it depends on something other than the function parameters. And if that's the case then caching its result is a bad idea anyway.
What am I missing here?
This isn't true. The canonical example is trying to grab the first element of an empty list. You either check and catch the error or eat the exceptional behavior, but some pure functions are just partial and aren't defined for all inputs.
(Whether or not they throw a catch-able exception depends on language/implementation and is really not relevant to my point.)
This comes up a lot in the Haskell community -- code outside IO is guaranteed by the compiler to be pure, but you can still get hit with an asynchronous exception because you called a partial function.
Don't use exceptions. Not supported.
Theoretical C++ has tons of stuff. Practical C++ doesn't use most of them :D
Exceptions have been quite well supported by all major compilers on all major platforms for a very long time.
And that is really puzzling me. Why do tutorials (from 20 years ago) starts with "cout << hello world;" ???
> Exceptions have been quite well supported by all major compilers on all major platforms for a very long time.
Try throwing exceptions in the kernel or in a driver, see how well it goes, or try in an embedded device. (That is, when it can be done in C++, and not C only).
On the other hand, C doesn't seem to attract the same type of programmers because the language itself is relatively spartan. I have a theory that simpler languages naturally encourage programmers to find simpler solutions both for themselves and the machine (thus possibly being more efficient), because more complex ones would involve more work. Asm is an extreme example, where you have to basically write every instruction the machine will execute --- and in such a scenario, even if it's mostly library calls, you're not going to want to construct and destruct a dozen objects unless you absolutely, unconditionally need to.
AbstractSingletonProxyFactoryBean
QED.
And simpler solutions for their users. Maybe a good thing; maybe not. But it's unlikely you will see a modern web browser in C.
In C, I have to maintain careful discipline in memory management. In C++, the same operators used for this in C will now sometimes do this for you, except when they don't. And so I look at C++ code and I have no idea what it's doing, and I don't know if it's safe.
In Python or whatever I needn't worry because there aren't any actual pointers or heap corruptions or segfaults.
Mind you, I have very little experience writing C++ code, so maybe I'd get used to it.
So, unsafe.
C is a car made in the days when having a safety belt wasn't even a concept, so there is no option to buckle it.
Alternatively you can take it the other route and discard RAII but then you're basically just writing C.
Some of the most heinous, insane bugs I have ever seen are because C++ is a complexity nightmare and very intelligent people struggle to get things right. Rust is orders of magnitude safer as it forces you to play with safe constructs only (avoiding surprises left by others, or past you) and wrapping anything explicitly unsafe.
[1] https://blogs.msdn.microsoft.com/vcblog/2016/03/31/c-core-gu...
[2] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus
But its insistence on $GOPATH is infuriating.
I have many, many projects in dozens of languages, organised in a way that reflects both my development and deployment practices.
But Go wants to be different. And it wants to be separate.
It's a tiny knitpick, and there is much I like about Go, even the way it handles exceptions. But it inconveniences me. And so, I guess I'll pout.
If you have a ProjectX with the path $pathx, set $GOTPATH to $pathx and place your source files in $pathx/src/projectx
I also hate that I have to do this though and I totally understand what you mean. Just wanted to mention my favorite work-around to see if I get some feedback on it being stupid/clever/ordinary.
I can use system libs if available, and if not, drag down the latest, build that locally, and link locally.
Go isn't as flexible. It's opinionated.
There doesn't seem to be any benefit other than standards compliance in having a GOPATH.
I have to circumvent how Go wants to work so that it can fit with the standards I have that C, C++, C#, F#, Haskell, D, Nim, Red, Chicken, Python all work with without being bashed into submission, they supply tools to make it easier.
They don't have lead developers standing up and saying "All Python libraries should be developed in this particular folder structure. Even if it makes integrating C code more difficult."
That feels... Wrong.
So repeatedly setting GOPATH sucks, and can cause havoc if the envar isn't released properly by, say, a failing build.
But, as I said to start with: This is a tiny knitpick.
It means I come back and try Go, time and again.
It also means I spend more time wrestling Go into submission within my automated testing and deployment structures, than writing code in it.
You can also write your own wrapper script around Go build tools. Or even bypass `go build` entirely and call the Go compiler in the exact same way you'd described above (-o and -L flags included). Run `go build -n` against your project to see what `go build` does (it's print only mode, no compiling will happen) and you'll see it's really no different to what you were asking for
So everything you want to do can and is supported by Go. It's just not it's default behaviour so you needed to do a little digging to find it.
Personally though, I find the inconvenience of having to change GOPATH / GOOS / GOARCH to be significantly less annoying than having to write makefiles and configure scripts to handle every platform I might want to target. For most people `go build` abstracts away the pain of cross compiling and build scripts quite nicely. In fact that was one of the biggest reasons I started using Go in the first place (I wanted something I could rapidly code and compile on AMD64 then rsync onto ARM devices back when I was experimenting with building custom embedded devices for my car).
edit: just want to add that I'm not under any illusion that Go nor it's tooling is perfect. I'm just saying Go can easily support your specific workflow preferences even though superficially it appears otherwise.
I would love some documentation on it. The Go developers hostility to the approach is hugely offputting.
If you don't mind me saying, maybe some of the hostility is because you keep saying Go "cannot" do x, y or z without looking at said documentation first? I know I've gotten terse responses from the community in the past when I've asked questions that were already well explained in the docs. I agree it's not nice being on the receiving end of such responses but I can also sympathise with why the community get sick of answering basic questions that are already available in a simple Google / DuckDuckGo search.
> 1) ... The $GOPATH provides us with a place to store common, shared third party libs on our system, and our own projects.
> #1 is the correct and supported way. The other methods are actively discouraged. Andrew.
But, for example, the documentation for GOPATH makes no suggestion it is even possible [1]:
> GOPATH must be set to get, build and install packages outside the standard Go tree.
> Each directory listed in GOPATH must have a prescribed structure:
And the compiler help says:
> Build adheres to certain conventions such as those described by 'go help gopath'.
It's exciting that I've been mislead about the ease of this, but I wouldn't say it is well explained in the docs.
The team want one true way of building a go pkg.
[0] https://groups.google.com/forum/#!topic/golang-nuts/dxOFYPpJ...
[1] https://golang.org/cmd/go/#hdr-GOPATH_environment_variable
> But, for example, the documentation for GOPATH makes no suggestion it is even possible [1]
Well to be fair you were looking for information about compiler flags in the documentation for the envvars; which perhaps wasn't the best systematic approach to research your problem? But hopefully you now have the information you need? Or at least better idea of how to find the answers you seek?
>The team want one true way of building a go pkg.
This is very true. However as long as your code follows the same standards it doesn't really matter how you chose to organise your source files as it should still compile on other people's systems using `go build` in the idiomatic way. And for what it's worth, a lot of people don't follow the directory hierarchy in the strictest way and/or use `go build/install`; choosing instead to use their preferred vendoring system. So I doubt many people would have a problem if you wanted to do your own thing just so long as the code is still compatible with Go's standard build tools should you release your projects to the world.
I understand that different people find themselves preferring different programming languages based on their syntax, features, tooling, etc. But I do think it would be a shame not to use a language you conceptually like just because of a single and easily changeable environmental variable.
Am I wrong in my understanding?
As though compilers aren't themselves written by humans.
When abstractions move into the language itself, it's harder to observe what's going on when they do something strange (even if it's correct) than if they were just some other library code in the same program.
I remember back in 98 when first picking up Java, thinking, WHAT NO MACROS?, but now i believe that was an incredibly good idea not having them. Of course Java now has things like aspects, et. al, so you never can get away from things if you really want them.
The only thing to know about C and C++. Don't use any of them unless there is no choice.